Как написать калькулятор на ассемблере

Permalink


@IbrahiimKhalil

1

contributor

Users who have contributed to this file


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

org 100h
jmp start ; jump over data declaration
msg: db «1-Add»,0dh,0ah,«2-Multiply»,0dh,0ah,«3-Subtract»,0dh,0ah,«4-Divide», 0Dh,0Ah, ‘$’
msg2: db 0dh,0ah,«Enter First No : $»
msg3: db 0dh,0ah,«Enter Second No : $»
msg4: db 0dh,0ah,«Choice Error $»
msg5: db 0dh,0ah,«Result : $»
msg6: db 0dh,0ah ,‘thank you for using the calculator! press any key… ‘, 0Dh,0Ah, ‘$’
start: mov ah,9
mov dx, offset msg ;first we will display hte first message from which he can choose the operation using int 21h
int 21h
mov ah,0
int 16h ;then we will use int 16h to read a key press, to know the operation he choosed
cmp al,31h ;the keypress will be stored in al so, we will comapre to 1 addition ……….
je Addition
cmp al,32h
je Multiply
cmp al,33h
je Subtract
cmp al,34h
je Divide
mov ah,09h
mov dx, offset msg4
int 21h
mov ah,0
int 16h
jmp start
Addition: mov ah,09h ;then let us handle the case of addition operation
mov dx, offset msg2 ;first we will display this message enter first no also using int 21h
int 21h
mov cx,0 ;we will call InputNo to handle our input as we will take each number seprately
call InputNo ;first we will move to cx 0 because we will increment on it later in InputNo
push dx
mov ah,9
mov dx, offset msg3
int 21h
mov cx,0
call InputNo
pop bx
add dx,bx
push dx
mov ah,9
mov dx, offset msg5
int 21h
mov cx,10000
pop dx
call View
jmp exit
InputNo: mov ah,0
int 16h ;then we will use int 16h to read a key press
mov dx,0
mov bx,1
cmp al,0dh ;the keypress will be stored in al so, we will comapre to 0d which represent the enter key, to know wheter he finished entering the number or not
je FormNo ;if it’s the enter key then this mean we already have our number stored in the stack, so we will return it back using FormNo
sub ax,30h ;we will subtract 30 from the the value of ax to convert the value of key press from ascii to decimal
call ViewNo ;then call ViewNo to view the key we pressed on the screen
mov ah,0 ;we will mov 0 to ah before we push ax to the stack bec we only need the value in al
push ax ;push the contents of ax to the stack
inc cx ;we will add 1 to cx as this represent the counter for the number of digit
jmp InputNo ;then we will jump back to input number to either take another number or press enter
;we took each number separatly so we need to form our number and store in one bit for example if our number 235
FormNo: pop ax
push dx
mul bx
pop dx
add dx,ax
mov ax,bx
mov bx,10
push dx
mul bx
pop dx
mov bx,ax
dec cx
cmp cx,0
jne FormNo
ret
View: mov ax,dx
mov dx,0
div cx
call ViewNo
mov bx,dx
mov dx,0
mov ax,cx
mov cx,10
div cx
mov dx,bx
mov cx,ax
cmp ax,0
jne View
ret
ViewNo: push ax ;we will push ax and dx to the stack because we will change there values while viewing then we will pop them back from
push dx ;the stack we will do these so, we don’t affect their contents
mov dx,ax ;we will mov the value to dx as interrupt 21h expect that the output is stored in it
add dl,30h ;add 30 to its value to convert it back to ascii
mov ah,2
int 21h
pop dx
pop ax
ret
exit: mov dx,offset msg6
mov ah, 09h
int 21h
mov ah, 0
int 16h
ret
Multiply: mov ah,09h
mov dx, offset msg2
int 21h
mov cx,0
call InputNo
push dx
mov ah,9
mov dx, offset msg3
int 21h
mov cx,0
call InputNo
pop bx
mov ax,dx
mul bx
mov dx,ax
push dx
mov ah,9
mov dx, offset msg5
int 21h
mov cx,10000
pop dx
call View
jmp exit
Subtract: mov ah,09h
mov dx, offset msg2
int 21h
mov cx,0
call InputNo
push dx
mov ah,9
mov dx, offset msg3
int 21h
mov cx,0
call InputNo
pop bx
sub bx,dx
mov dx,bx
push dx
mov ah,9
mov dx, offset msg5
int 21h
mov cx,10000
pop dx
call View
jmp exit
Divide: mov ah,09h
mov dx, offset msg2
int 21h
mov cx,0
call InputNo
push dx
mov ah,9
mov dx, offset msg3
int 21h
mov cx,0
call InputNo
pop bx
mov ax,bx
mov cx,dx
mov dx,0
mov bx,0
div cx
mov bx,dx
mov dx,ax
push bx
push dx
mov ah,9
mov dx, offset msg5
int 21h
mov cx,10000
pop dx
call View
pop bx
cmp bx,0
je exit
jmp exit

Время на прочтение
16 мин

Количество просмотров 117K

В наше время редко возникает необходимость писать на чистом ассемблере, но я определённо рекомендую это всем, кто интересуется программированием. Вы увидите вещи под иным углом, а навыки пригодятся при отладке кода на других языках.

В этой статье мы напишем с нуля калькулятор обратной польской записи (RPN) на чистом ассемблере x86. Когда закончим, то сможем использовать его так:

$ ./calc "32+6*" # "(3+2)*6" в инфиксной нотации
30

Весь код для статьи здесь. Он обильно закомментирован и может служить учебным материалом для тех, кто уже знает ассемблер.

Начнём с написания базовой программы Hello world! для проверки настроек среды. Затем перейдём к системным вызовам, стеку вызовов, стековым кадрам и соглашению о вызовах x86. Потом для практики напишем некоторые базовые функции на ассемблере x86 — и начнём писать калькулятор RPN.

Предполагается, что у читателя есть некоторый опыт программирования на C и базовые знания компьютерной архитектуры (например, что такое регистр процессора). Поскольку мы будем использовать Linux, вы также должны уметь использовать командную строку Linux.

Настройка среды

Как уже сказано, мы используем Linux (64- или 32-битный). Приведённый код не работает в Windows или Mac OS X.

Для установки нужен только компоновщик GNU ld из binutils, который предварительно установлен в большинстве дистрибутивов, и ассемблер NASM. На Ubuntu и Debian можете установить и то, и другое одной командой:

$ sudo apt-get install binutils nasm

Я бы также рекомендовал держать под рукой таблицу ASCII.

Hello, world!

Для проверки среды сохраните следующий код в файле calc.asm:

; Компоновщик находит символ _start и начинает выполнение программы
; отсюда.
global _start

; В разделе .rodata хранятся константы (только для чтения)
; Порядок секций не имеет значения, но я люблю ставить её вперёд
section .rodata
    ; Объявляем пару байтов как hello_world. Псевдоинструкция базы NASM 
    ; допускает однобайтовое значение, строковую константу или их сочетание,
    ; как здесь. 0xA = новая строка, 0x0 = нуль окончания строки
    hello_world: db "Hello world!", 0xA, 0x0

; Начало секции .text, где находится код программы
section .text
_start:
    mov eax, 0x04           ; записать число 4 в регистр eax (0x04 = write())
    mov ebx, 0x1            ; дескриптор файла (1 = стандартный вывод, 2 = стандартная ошибка)
    mov ecx, hello_world    ; указатель на выводимую строку
    mov edx, 14             ; длина строки
    int 0x80                ; отправляем сигнал прерывания 0x80, который ОС
                            ;   интерпретирует как системный вызов

    mov eax, 0x01           ; 0x01 = exit()
    mov ebx, 0              ; 0 = нет ошибок
    int 0x80

Комментарии объясняют общую структуру. Список регистров и общих инструкций можете изучить в «Руководстве по ассемблеру x86 университета Вирджинии». При дальнейшем обсуждении системных вызовов это тем более понадобится.

Следующие команды собирают файл ассемблера в объектный файл, а затем компонует исполняемый файл:

$ nasm -f elf_i386 calc.asm -o calc
$ ld -m elf_i386 calc.o -o calc

После запуска вы должны увидеть:

$ ./calc
Hello world!

Makefile

Это необязательная часть, но для упрощения сборки и компоновки в будущем можно сделать Makefile. Сохраните его в том же каталоге, что и calc.asm:

CFLAGS= -f elf32
LFLAGS= -m elf_i386

all: calc

calc: calc.o
	ld $(LFLAGS) calc.o -o calc

calc.o: calc.asm
	nasm $(CFLAGS) calc.asm -o calc.o

clean:
	rm -f calc.o calc
        
.INTERMEDIATE: calc.o

Затем вместо вышеприведённых инструкций просто запускаем make.

Системные вызовы

Системные вызовы Linux указывают ОС выполнить для нас какие-то действия. В этой статье мы используем только два системных вызова: write() для записи строки в файл или поток (в нашем случае это стандартное устройство вывода и стандартная ошибка) и exit() для выхода из программы:

syscall 0x01: exit(int error_code)
  error_code - используем 0 для выхода без ошибок и любые другие значения (такие как 1) для ошибок
syscall 0x04: write(int fd, char *string, int length)
  fd — используем 1 для стандартного вывода, 2 для стандартного потока вывода ошибок
  string — указатель на первый символ строки
  length — длина строки в байтах

Системные вызовы настраиваются путём сохранения номера системного вызова в регистре eax, а затем его аргументов в ebx, ecx, edx в таком порядке. Можете заметить, что у exit() только один аргумент — в этом случае ecx и edx не имеют значения.

eax ebx ecx edx
Номер системного вызова arg1 arg2 arg3

Стек вызовов

Стек вызовов — структура данных, в которой хранится информация о каждом обращении к функции. У каждого вызова собственный раздел в стеке — «фрейм». Он хранит некоторую информацию о текущем вызове: локальные переменные этой функции и адрес возврата (куда программа должна перейти после выполнения функции).

Сразу отмечу одну неочевидную вещь: стек увеличивается вниз по памяти. Когда вы добавляете что-то на верх стека, оно вставляется по адресу памяти ниже, чем предыдущий элемент. Другими словами, по мере роста стека адрес памяти в верхней части стека уменьшается. Чтобы избежать путаницы, я буду всё время напоминать об этом факте.

Инструкция push заносит что-нибудь на верх стека, а pop уносит данные оттуда. Например, push еах выделяет место наверху стека и помещает туда значение из регистра eax, а pop еах переносит любые данные из верхней части стека в eax и освобождает эту область памяти.

Цель регистра esp — указать на вершину стека. Любые данные выше esp считаются не попавшими в стек, это мусорные данные. Выполнение инструкции push (или pop) перемещает esp. Вы можете манипулировать esp и напрямую, если отдаёте отчёт своим действиям.

Регистр ebp похож на esp, только он всегда указывает примерно на середину текущего кадра стека, непосредственно перед локальными переменными текущей функции (поговорим об этом позже). Однако вызов другой функции не перемещает ebp автоматически, это нужно каждый раз делать вручную.

Соглашение о вызовах для архитектуры x86

В х86 нет встроенного понятия функции как в высокоуровневых языках. Инструкция call — это по сути просто jmp (goto) в другой адрес памяти. Чтобы использовать подпрограммы как функции в других языках (которые могут принимать аргументы и возвращать данные обратно), нужно следовать соглашению о вызовах (существует много конвенций, но мы используем CDECL, самое популярное соглашение для x86 среди компиляторов С и программистов на ассемблере). Это также гарантирует, что регистры подпрограммы не перепутаются при вызове другой функции.

Правила вызывающей стороны

Перед вызовом функции вызывающая сторона должна:

  1. Сохранить в стек регистры, которые обязан сохранять вызывающий. Вызываемая функция может изменить некоторые регистры: чтобы не потерять данные, вызывающая сторона должна сохранить их в памяти до помещения в стек. Речь идёт о регистрах eax, ecx и edx. Если вы не используете какие-то из них, то их можно не сохранять.
  2. Записать аргументы функции на стек в обратном порядке (сначала последний аргумент, в конце первый аргумент). Такой порядок гарантирует, что вызываемая функция получит из стека свои аргументы в правильном порядке.
  3. Вызвать подпрограмму.

По возможности функция сохранит результат в eax. Сразу после call вызывающая сторона должна:

  1. Удалить из стека аргументы функции. Обычно это делается путём простого добавления числа байтов в esp. Не забывайте, что стек растёт вниз, поэтому для удаления из стека необходимо добавить байты.
  2. Восстановить сохранённые регистры, забрав их из стека в обратном порядке инструкцией pop. Вызываемая функция не изменит никакие другие регистры.

Следующий пример демонстрирует, как применяются эти правила. Предположим, что функция _subtract принимает два целочисленных (4-байтовых) аргумента и возвращает первый аргумент за вычетом второго. В подпрограмме _mysubroutine вызываем _subtract с аргументами 10 и 2:

_mysubroutine:
    ; ...
    ; здесь какой-то код
    ; ...
    push ecx       ; сохраняем регистры (я решил не сохранять eax)
    push edx
    push 2         ; второе правило, пушим аргументы в обратном порядке
    push 10
    call _subtract ; eax теперь равен 10-2=8
    add esp, 8     ; удаляем 8 байт со стека (два аргумента по 4 байта)
    pop edx        ; восстанавливаем сохранённые регистры
    pop ecx
    ; ...
    ; ещё какой-то код, где я использую удивительно полезное значение из eax
    ; ...

Правила вызываемой подпрограммы

Перед вызовом подпрограмма должна:

  1. Сохранить указатель базового регистра ebp предыдущего фрейма, записав его на стек.
  2. Отрегулировать ebp с предыдущего фрейма на текущий (текущее значение esp).
  3. Выделить больше места в стеке для локальных переменных, при необходимости переместить указатель esp. Поскольку стек растёт вниз, нужно вычесть недостающую память из esp.
  4. Сохранить в стек регистры вызываемой подпрограммы. Это ebx, edi и esi. Необязательно сохранять регистры, которые не планируется изменять.

Стек вызовов после шага 1:

Стек вызовов после шага 2:

Стек вызовов после шага 4:

На этих диаграммах в каждом стековом фрейме указан адрес возврата. Его автоматически вставляет в стек инструкция call. Инструкция ret извлекает адрес с верхней части стека и переходит на него. Эта инструкция нам не нужна, я просто показал, почему локальные переменные функции находятся на 4 байта выше ebp, но аргументы функции — на 8 байт ниже ebp.

На последней диаграмме также можно заметить, что локальные переменные функции всегда начинается на 4 байта выше ebp с адреса ebp-4 (здесь вычитание, потому что мы двигаемся вверх по стеку), а аргументы функции всегда начинается на 8 байт ниже ebp с адреса ebp+8 (сложение, потому что мы двигаемся вниз по стеку). Если следовать правилам из этой конвенции, так будет c переменными и аргументами любой функции.

Когда функция выполнена и вы хотите вернуться, нужно сначала установить eax на возвращаемое значение функции, если это необходимо. Кроме того, нужно:

  1. Восстановить сохранённые регистры, вынеся их из стека в обратном порядке.
  2. Освободить место в стеке, выделенное локальным переменным на шаге 3, если необходимо: делается простой установкой esp в ebp
  3. Восстановить указатель базы ebp предыдущего фрейма, вынеся его из стека.
  4. Вернуться с помощью ret

Теперь реализуем функцию _subtract из нашего примера:

_subtract:
    push ebp           ; сохранение указателя базы предыдущего фрейма
    mov ebp, esp        ; настройка ebp
    ; Здесь я бы выделил место на стеке для локальных переменных, но они мне не нужны
    ; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не
    ; собираюсь изменять
    ; Тут начинается функция
    mov eax, [ebp+8]    ; копирование первого аргумента функции в eax. Скобки
                        ; означают доступ к памяти по адресу ebp+8
    sub eax, [ebp+12]   ; вычитание второго аргумента по адресу ebp+12 из первого 
                        ; аргумента
    ; Тут функция заканчивается, eax равен её возвращаемому значению
    ; Здесь я бы восстановил регистры, но они не сохранялись
    ; Здесь я бы освободил стек от переменных, но память для них не выделялась
    pop ebp             ; восстановление указателя базы предыдущего фрейма
    ret

Вход и выход

В приведённом примере вы можете заметить, что функция всегда запускается одинаково: push ebp, mov ebp, esp и выделение памяти для локальных переменных. В наборе x86 есть удобная инструкция, которая всё это выполняет: enter a b, где a — количество байт, которые вы хотите выделить для локальных переменных, b — «уровень вложенности», который мы всегда будем выставлять на 0. Кроме того, функция всегда заканчивается инструкциями pop ebp и mov esp, ebp (хотя они необходимы только при выделении памяти для локальных переменных, но в любом случае не причиняют вреда). Это тоже можно заменить одной инструкцией: leave. Вносим изменения:

_subtract:
    enter 0, 0            ; сохранение указателя базы предыдущего фрейма и настройка ebp
    ; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не 
    ; собираюсь изменять
    ; Тут начинается функция
    mov eax, [ebp+8]    ; копирование первого аргумента функции в eax. Скобки
                        ; означают доступ к памяти по адресу ebp+8
    sub eax, [ebp+12]   ; вычитание второго аргумента по адресу ebp+12 из 
                        ; первого аргумента
    ; Тут функция заканчивается, eax равен её возвращаемому значению
    ; Здесь я бы восстановил регистры, но они не сохранялись
    leave              ; восстановление указателя базы предыдущего фрейма
    ret

Написание некоторых основных функций

Усвоив соглашение о вызовах, можно приступить к написанию некоторых подпрограмм. Почему бы не обобщить код, который выводит «Hello world!», для вывода любых строк: функция _print_msg.

Здесь понадобится ещё одна функция _strlen для подсчёта длины строки. На C она может выглядеть так:

size_t strlen(char *s) {
    size_t length = 0;
    while (*s != 0)
    {           // начало цикла
        length++;
        s++;
    }           // конец цикла
    return length;
}

Другими словами, с самого начала строки мы добавляем 1 к возвращаемым значением для каждого символа, кроме нуля. Как только замечен нулевой символ, возвращаем накопленное в цикле значение. В ассемблере это тоже довольно просто: можно использовать как базу ранее написанную функцию _subtract:

_strlen:
    enter 0, 0          ; сохраняем указатель базы предыдущего фрейма и настраиваем ebp
    ; Здесь я бы сохранил регистры вызываемой подпрограммы, но я ничего не 
    ; собираюсь изменять
    ; Здесь начинается функция
    mov eax, 0          ; length = 0
    mov ecx, [ebp+8]    ; первый аргумент функции (указатель на первый
                        ; символ строки) копируется в ecx (его сохраняет вызывающая 
                        ; сторона, так что нам нет нужды сохранять)
_strlen_loop_start:     ; это метка, куда можно перейти
    cmp byte [ecx], 0   ; разыменование указателя и сравнение его с нулём. По
                        ; умолчанию память считывается по 32 бита (4 байта).
                        ; Иное нужно указать явно. Здесь мы указываем
                        ; чтение только одного байта (один символ)
    je _strlen_loop_end ; выход из цикла при появлении нуля
    inc eax             ; теперь мы внутри цикла, добавляем 1 к возвращаемому значению
    add ecx, 1          ; переход к следующему символу в строке
    jmp _strlen_loop_start  ; переход обратно к началу цикла
_strlen_loop_end:
    ; Здесь функция заканчивается, eax равно возвращаемому значению
    ; Здесь я бы восстановил регистры, но они не сохранялись
    leave               ; восстановление указателя базы предыдущего фрейма
    ret

Уже неплохо, верно? Сначала написать код на C может помочь, потому что большая его часть непосредственно преобразуется в ассемблер. Теперь можно использовать эту функцию в _print_msg, где мы применим все полученные знания:

_print_msg:
    enter 0, 0
    ; Здесь начинается функция
    mov eax, 0x04       ; 0x04 = системный вызов write()
    mov ebx, 0x1        ; 0x1 = стандартный вывод
    mov ecx, [ebp+8]    ; мы хотим вывести первый аргумент этой функции,
    ; сначала установим edx на длину строки. Пришло время вызвать _strlen
    push eax            ; сохраняем регистры вызываемой функции (я решил не сохранять edx)
    push ecx       
    push dword [ebp+8]  ; пушим аргумент _strlen в _print_msg. Здесь NASM
                        ; ругается, если не указать размер, не знаю, почему.
                        ; В любом случае указателем будет dword (4 байта, 32 бита)
    call _strlen        ; eax теперь равен длине строки
    mov edx, eax        ; перемещаем размер строки в edx, где он нам нужен
    add esp, 4          ; удаляем 4 байта со стека (один 4-байтовый аргумент char*)
    pop ecx             ; восстанавливаем регистры вызывающей стороны
    pop eax
    ; мы закончили работу с функцией _strlen, можно инициировать системный вызов
    int 0x80
    leave
    ret

И посмотрим плоды нашей тяжёлой работы, используя эту функцию в полной программе “Hello, world!”.

_start:
    enter 0, 0
    ; сохраняем регистры вызывающей стороны (я решил никакие не сохранять)
    push hello_world    ; добавляем аргумент для _print_msg
    call _print_msg
    mov eax, 0x01           ; 0x01 = exit()
    mov ebx, 0              ; 0 = без ошибок
    int 0x80

Хотите верьте, хотите нет, но мы рассмотрели все основные темы, которые нужны для написания базовых программ на ассемблере x86! Теперь у нас есть весь вводный материал и теория, так что полностью сосредоточимся на коде и применим полученные знания для написания нашего калькулятора RPN. Функции будут намного длиннее и даже станут использовать некоторые локальные переменные. Если хотите сразу увидеть готовую программу, вот она.

Для тех из вас, кто не знаком с обратной польской записью (иногда называемой обратной польской нотацией или постфиксной нотацией), то здесь выражения вычисляются с помощью стека. Поэтому нужно создать стек, а также функции _pop и _push для манипуляций с этим стеком. Понадобится ещё функция _print_answer, которая выведет в конце вычислений строковое представление числового результата.

Создание стека

Сначала определим для нашего стека пространство в памяти, а также глобальную переменную stack_size. Желательно изменить эти переменные так, чтобы они попали не в раздел .rodata, а в .data.

section .data
    stack_size: dd 0        ; создаём переменную dword (4 байта) со значением 0
    stack: times 256 dd 0   ; заполняем стек нулями

Теперь можно реализовать функции _push и _pop:

_push:
    enter 0, 0
    ; Сохраняем регистры вызываемой функции, которые будем использовать
    push eax
    push edx
    mov eax, [stack_size]
    mov edx, [ebp+8]
    mov [stack + 4*eax], edx    ; Заносим аргумент на стек. Масштабируем по
                                ; четыре байта в соответствии с размером dword
    inc dword [stack_size]      ; Добавляем 1 к stack_size
    ; Восстанавливаем регистры вызываемой функции
    pop edx
    pop eax
    leave
    ret

_pop:
    enter 0, 0
    ;  Сохраняем регистры вызываемой функции
    dec dword [stack_size]      ; Сначала вычитаем 1 из stack_size
    mov eax, [stack_size]
    mov eax, [stack + 4*eax]    ; Заносим число на верх стека в eax
    ; Здесь я бы восстановил регистры, но они не сохранялись
    leave
    ret

Вывод чисел

_print_answer намного сложнее: придётся конвертировать числа в строки и использовать несколько других функций. Понадобится функция _putc, которая выводит один символ, функция mod для вычисления остатка от деления (модуля) двух аргументов и _pow_10 для возведения в степень 10. Позже вы поймёте, зачем они нужны. Это довольно просто, вот код:

_pow_10:
    enter 0, 0
    mov ecx, [ebp+8]    ; задаёт ecx (сохранённый вызывающей стороной) аргументом 
                        ; функции
    mov eax, 1          ; первая степень 10 (10**0 = 1)
_pow_10_loop_start:     ; умножает eax на 10, если ecx не равно 0
    cmp ecx, 0
    je _pow_10_loop_end
    imul eax, 10
    sub ecx, 1
    jmp _pow_10_loop_start
_pow_10_loop_end:
    leave
    ret

_mod:
    enter 0, 0
    push ebx
    mov edx, 0          ; объясняется ниже
    mov eax, [ebp+8]
    mov ebx, [ebp+12]
    idiv ebx            ; делит 64-битное целое [edx:eax] на ebx. Мы хотим поделить
                        ; только 32-битное целое eax, так что устанавливаем edx равным 
                        ; нулю.
                        ; частное сохраняем в eax, остаток в edx. Как обычно, получить 
                        ; информацию по конкретной инструкции можно из справочников, 
                        ; перечисленных в конце статьи.
    mov eax, edx        ; возвращает остаток от деления (модуль)
    pop ebx
    leave
    ret

_putc:
    enter 0, 0
    mov eax, 0x04       ; write()
    mov ebx, 1          ; стандартный вывод
    lea ecx, [ebp+8]    ; входной символ
    mov edx, 1          ; вывести только 1 символ
    int 0x80
    leave
    ret

Итак, как мы выводим отдельные цифры в числе? Во-первых, обратите внимание, что последняя цифра числа равна остатку от деления на 10 (например, 123 % 10 = 3), а следующая цифра — это остаток от деления на 100, поделенный на 10 (например, (123 % 100)/10 = 2). В общем, можно найти конкретную цифру числа (справа налево), найдя (число % 10**n) / 10**(n-1), где число единиц будет равно n = 1, число десятков n = 2 и так далее.

Используя это знание, можно найти все цифры числа с n = 1 до n = 10 (это максимальное количество разрядов в знаковом 4-байтовом целом). Но намного проще идти слева направо — так мы сможем печатать каждый символ, как только находим его, и избавиться от нулей в левой части. Поэтому перебираем числа от n = 10 до n = 1.

На C программа будет выглядеть примерно так:

#define MAX_DIGITS 10
void print_answer(int a) {
    if (a < 0) { // если число отрицательное
        putc('-'); // вывести знак «минус»
        a = -a; // преобразовать в положительное число
    }
    int started = 0;
    for (int i = MAX_DIGITS; i > 0; i--) {
        int digit = (a % pow_10(i)) / pow_10(i-1);
        if (digit == 0 && started == 0) continue; // не выводить лишние нули
        started = 1;
        putc(digit + '0');
    }
}

Теперь вы понимаете, зачем нам эти три функции. Давайте реализуем это на ассемблере:

%define MAX_DIGITS 10

_print_answer:
    enter 1, 0              ; используем 1 байт для переменной "started" в коде C
    push ebx
    push edi
    push esi
    mov eax, [ebp+8]        ; наш аргумент "a"
    cmp eax, 0              ; если число не отрицательное, пропускаем этот условный 
                            ; оператор
    jge _print_answer_negate_end
    ; call putc for '-'
    push eax
    push 0x2d               ; символ '-'
    call _putc
    add esp, 4
    pop eax
    neg eax                 ; преобразуем в положительное число
_print_answer_negate_end:
    mov byte [ebp-4], 0     ; started = 0
    mov ecx, MAX_DIGITS     ; переменная i
_print_answer_loop_start:
    cmp ecx, 0
    je _print_answer_loop_end
    ; вызов pow_10 для ecx. Попытаемся сделать ebx как переменную "digit" в коде C.
    ; Пока что назначим edx = pow_10(i-1), а ebx = pow_10(i)
    push eax
    push ecx
    dec ecx             ; i-1
    push ecx            ; первый аргумент для _pow_10
    call _pow_10
    mov edx, eax        ; edx = pow_10(i-1)
    add esp, 4
    pop ecx             ; восстанавливаем значение i для ecx
    pop eax
    ; end pow_10 call
    mov ebx, edx        ; digit = ebx = pow_10(i-1)
    imul ebx, 10        ; digit = ebx = pow_10(i)
    ; вызываем _mod для (a % pow_10(i)), то есть (eax mod ebx)
    push eax
    push ecx
    push edx
    push ebx            ; arg2, ebx = digit = pow_10(i)
    push eax            ; arg1, eax = a
    call _mod
    mov ebx, eax        ; digit = ebx = a % pow_10(i+1), almost there
    add esp, 8
    pop edx
    pop ecx
    pop eax
    ; завершение вызова mod
    ; делим ebx (переменная "digit" ) на pow_10(i) (edx). Придётся сохранить пару 
    ; регистров, потому что idiv использует для деления и edx, eax. Поскольку 
    ; edx является нашим делителем, переместим его в какой-нибудь 
    ; другой регистр
    push esi
    mov esi, edx
    push eax
    mov eax, ebx
    mov edx, 0
    idiv esi            ; eax хранит результат (цифру)
    mov ebx, eax        ; ebx = (a % pow_10(i)) / pow_10(i-1), переменная "digit" в коде C
    pop eax
    pop esi
    ; end division
    cmp ebx, 0                        ; если digit == 0
    jne _print_answer_trailing_zeroes_check_end
    cmp byte [ebp-4], 0               ; если started == 0
    jne _print_answer_trailing_zeroes_check_end
    jmp _print_answer_loop_continue   ; continue
_print_answer_trailing_zeroes_check_end:
    mov byte [ebp-4], 1     ; started = 1
    add ebx, 0x30           ; digit + '0'
    ; вызов putc
    push eax
    push ecx
    push edx
    push ebx
    call _putc
    add esp, 4
    pop edx
    pop ecx
    pop eax
    ; окончание вызова putc
_print_answer_loop_continue:
    sub ecx, 1
    jmp _print_answer_loop_start
_print_answer_loop_end:
    pop esi
    pop edi
    pop ebx
    leave
    ret

Это было тяжкое испытание! Надеюсь, комментарии помогают разобраться. Если вы сейчас думаете: «Почему нельзя просто написать printf("%d")?», то вам понравится окончание статьи, где мы заменим функцию именно этим!

Теперь у нас есть все необходимые функции, осталось реализовать основную логику в _start — и на этом всё!

Вычисление обратной польской записи

Как мы уже говорили, обратная польская запись вычисляется с помощью стека. При чтении число заносится на стек, а при чтении оператор применяется к двум объектам наверху стека.

Например, если мы хотим вычислить 84/3+6* (это выражение также можно записать в виде 6384/+*), процесс выглядит следующим образом:

Шаг Символ Стек перед Стек после
1 8 [] [8]
2 4 [8] [8, 4]
3 / [8, 4] [2]
4 3 [2] [2, 3]
5 + [2, 3] [5]
6 6 [5] [5, 6]
7 * [5, 6] [30]

Если на входе допустимое постфиксное выражение, то в конце вычислений на стеке остаётся лишь один элемент — это и есть ответ, результат вычислений. В нашем случае число равно 30.

В ассемблере нужно реализовать нечто вроде такого кода на C:

int stack[256];         // наверное, 256 слишком много для нашего стека
int stack_size = 0;

int main(int argc, char *argv[]) {
    char *input = argv[0];
    size_t input_length = strlen(input);
    
    for (int i = 0; i < input_length; i++) {
        char c = input[i];
        if (c >= '0' && c <= '9') { // если символ — это цифра
            push(c - '0'); // преобразовать символ в целое число и поместить в стек
        } else {
            int b = pop();
            int a = pop();
            if (c == '+') {
                push(a+b);
            } else if (c == '-') {
                push(a-b);
            } else if (c == '*') {
                push(a*b);
            } else if (c == '/') {
                push(a/b);
            } else {
                error("Invalid inputn");
                exit(1);
            }
        }
    }
    
    if (stack_size != 1) {
        error("Invalid inputn");
        exit(1);
    }
    
    print_answer(stack[0]);
    exit(0);
}

Теперь у нас имеются все функции, необходимые для реализации этого, давайте начнём.

_start:
    ; аргументы _start получаются не так, как в других функциях.
    ; вместо этого esp указывает непосредственно на argc (число аргументов), а 
    ; esp+4 указывает на argv. Следовательно, esp+4 указывает на название
    ; программы, esp+8 - на первый аргумент и так далее
    mov esi, [esp+8]         ; esi = "input" = argv[0]
    ; вызываем _strlen для определения размера входных данных
    push esi
    call _strlen
    mov ebx, eax             ; ebx = input_length
    add esp, 4
    ; end _strlen call
    mov ecx, 0               ; ecx = "i"
_main_loop_start:
    cmp ecx, ebx             ; если (i >= input_length)
    jge _main_loop_end
    mov edx, 0
    mov dl, [esi + ecx]      ; то загрузить один байт из памяти в нижний байт
                             ; edx. Остальную часть edx обнуляем.
                             ; edx = переменная c = input[i]
    cmp edx, '0'
    jl _check_operator
    cmp edx, '9'
    jg _print_error
    sub edx, '0'
    mov eax, edx             ; eax = переменная c - '0' (цифра, не символ)
    jmp _push_eax_and_continue
_check_operator:
    ; дважды вызываем _pop для выноса переменной b в edi, a переменной b - в eax
    push ecx
    push ebx
    call _pop
    mov edi, eax             ; edi = b
    call _pop                ; eax = a
    pop ebx
    pop ecx
    ; end call _pop
    cmp edx, '+'
    jne _subtract
    add eax, edi                 ; eax = a+b
    jmp _push_eax_and_continue
_subtract:
    cmp edx, '-'
    jne _multiply
    sub eax, edi                 ; eax = a-b
    jmp _push_eax_and_continue
_multiply:
    cmp edx, '*'
    jne _divide
    imul eax, edi                ; eax = a*b
    jmp _push_eax_and_continue
_divide:
    cmp edx, '/'
    jne _print_error
    push edx                     ; сохраняем edx, потому что регистр обнулится для idiv
    mov edx, 0
    idiv edi                     ; eax = a/b
    pop edx
    ; теперь заносим eax на стек и продолжаем
_push_eax_and_continue:
    ; вызываем _push
    push eax
    push ecx
    push edx
    push eax          ; первый аргумент
    call _push
    add esp, 4
    pop edx
    pop ecx
    pop eax
    ; завершение call _push
    inc ecx
    jmp _main_loop_start
_main_loop_end:
    cmp byte [stack_size], 1      ; если (stack_size != 1), печать ошибки
    jne _print_error
    mov eax, [stack]
    push eax
    call _print_answer
    ; print a final newline
    push 0xA
    call _putc
    ; exit successfully
    mov eax, 0x01           ; 0x01 = exit()
    mov ebx, 0              ; 0 = без ошибок
    int 0x80                ; здесь выполнение завершается
_print_error:
    push error_msg
    call _print_msg
    mov eax, 0x01
    mov ebx, 1
    int 0x80

Понадобится ещё добавить строку error_msg в раздел .rodata:

section .rodata
    ; Назначаем на некоторые байты error_msg. Псевдоинструкция db в NASM
    ; позволяет использовать однобайтовое значение, строковую константу или их 
    ; сочетание. 0xA = новая строка, 0x0 = нуль окончания строки
    error_msg: db "Invalid input", 0xA, 0x0

И мы закончили! Удивите всех своих друзей, если они у вас есть. Надеюсь, теперь вы с большей теплотой отнесётесь к языкам высокого уровня, особенно если вспомнить, что многие старые программы писали полностью или почти полностью на ассемблере, например, оригинальный RollerCoaster Tycoon!

Весь код здесь. Спасибо за чтение! Могу продолжить, если вам интересно.

Дальнейшие действия

Можете попрактиковаться, реализовав несколько дополнительных функций:

  1. Выдать вместо segfault сообщение об ошибке, если программа не получает аргумент.
  2. Добавить поддержку дополнительных пробелов между операндами и операторами во входных данных.
  3. Добавить поддержку многоразрядных операндов.
  4. Разрешить ввод отрицательных чисел.
  5. Заменить _strlen на функцию из стандартной библиотеки C, а _print_answer заменить вызовом printf.

Дополнительные материалы

  • «Руководство по ассемблеру x86 университета Вирджинии» — более подробное изложение многих тем, рассмотренных нами, в том числе дополнительная информация по всем популярным инструкциям x86.
  • «Искусство выбора регистров Intel». Хотя большинство регистров x86 — регистры общего назначения, но у многих есть историческое значение. Следование этим соглашениям может улучшить читаемость кода и, как интересный побочный эффект, даже немного оптимизировать размер двоичных файлов.
  • NASM: Intel x86 Instruction Reference — полное руководство по всем малоизвестным инструкциям x86.

this is a basic calculator written in assembly, it has some nice features, it was developed for the laboratory of  the Computer Architecture course of the Computer Science Engineering on the USAC (so it is an useful tool for learning purposes)

  1. Written fully in assembly, no external library for input/output, just interruptions.
  2. Internally use a 128 bit representation for holding operators and result. The requirement was operate (+,-) with numbers up to 20 digits, and (*,/) with numbers up to 10 digits.
  3. Multiplication using repeated addition operations instead of the native “mult”.
  4. Divide by repeated subtraction and counting instead of the native “div”.
  5. Convert an ASCII String to Binary Number and vice versa. this is required to handle user input, and to display results.
  6. Basic Input/Output functions, like GetCh, PutCh, NewLine, WriteLine
  7. Custom Input/Output functions to read and write 128bit numbers, like Read128BitNum, Write128BitNum
  8. Basic Menu in Text Mode.
  9. Easy to modify, build. The package include the assembler and linker so the executable can be created anytime. (using DOSBox if you are running on a modern operating system)

For Example, in this image sequence, we can see a simple addition.

assembler calculator main menu

Calculator Main Menu

assembler calculator addition result

Calculator after execution of a addition operation.

And for large numbers (123456789 + 987654321)

assembler calculator with large numbers

Calculator after execution of an addition of two large numbers.

Some of the functions used for this project.

GetCh

this function just read a key from the keyboard and puts it’s ascii code on the AL register.

; function GetCh
; get ascii code key pressed, BIOS int

GetCh:

xor ah,ah ; clear ah
int 0x16 ; call interruption
ret ; return

Write

this function takes the address of any string and display on screen, the string must end with a “$” character.

; function Write
; display a string on screen
;
; parameter dx string address
; the string must end with a '$' character

Write:

push ax            ; backup ax 
mov ah,0x9         ; func 9, display on screen
int 0x21           ; DOS interruption
pop ax             ; restore ax
ret                ; return

Usage Example

mov dx, Msg1  ; 
call Write    ;

SEGMENT data  ; Segment containing initialized data

Msg1   DB "(1) ADD -> RESULT = A + B $"

NewLine

; function NewLine
; new line , chr(13) + chr(10) on screen

NewLine:

mov dx, CRLF   ;
call Write     ;
ret            ;

SEGMENT data   ; Segment containing initialized data

CRLF   DB   0DH,0AH,'$'

WriteLn

this fuction just aggregate the standar Write with the predefined CRLF string, to make a string with newline.

; function Writeln
; string + newline
; parameter dx address of string to print

Writeln:

call Write          ; Display the string proper through Write
mov dx,CRLF         ; Load offset of newline string to DX
call Write          ; Display the newline string through Write
ret                 ; Return to the caller

This Video Explain how to Use, Modify and Rebuild the Source Code.

Download Source Code

by [googleplusauthor]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
.model small
.stack 100h
 
data segment      
   _errorCF db 'Finded overflow at ADD operation!','$'  
   _errorOF db 'Finded overflow at SUB operation!','$'             
   _INFO    db 'Types of operations:',10,13,'1. + -> sum',10,13,'2. - -> subtraction',10,13,'$'           
   _nc      db 10,13,'$'
   first    dw 65535    ;первый операнд
   second   dw 5         ;второй операнд
data ends
   
code segment
assume cs:code,ds:data     
begin:
        mov ax,data         
        mov ds,ax           
        mov es,ax           ;es ссылается туда же, где и ds
;------------------------------------------------
;Вывод приглашения на ввод числа
        mov ah,09h          ;команда на вывод строки
        mov dx,offset _INFO ;смещение на строку
        int 21h             ;выполнить команду
;------------------------------------------------
;Определяем тип операции
enter_type_operation: 
        mov ah,01h            ;вызов команды ввода символа с клавиатуры
        int 21h               ;выполнить команду
        cmp al,'+'            ;введен "+" 
        je summ               ;да -> суммируем 2 числа
        cmp al,'-'            ;введен "-" 
        je minus              ;да -> вычитаем 2 числа
        jmp enter_type_operation;во всех других случаях - повтор ввода
;------------------------------------------------
;Суммирование двух чисел
summ:
        mov ah,09h            ;команда на вывод строки
        mov dx,offset _nc     ;в dx смещение на строку
        int 21h               ;выполнить команду
        mov ax,first          ;ax=first
        mov bx,second
        add ax,bx             ;ax=ax+bx
        jnc write_res         ;если нет переноса(CF=0), то вывод результата
        jmp errorCF           ;иначе вывести ошибку
;------------------------------------------------
minus:
        mov ah,09h            ;команда на вывод строки
        mov dx,offset _nc     ;в dx смещение на строку
        int 21h               ;выполнить команду
        mov ax,first          ;ax=first 
        mov bx,second
        sub ax,bx             ;ax=ax-bx
        jno write_res         ;если нет заема(OF=0), то вывод результата
        jmp errorOF           ;иначе вывести ошибку
;------------------------------------------------
;Вывод числа
write_res:
        test ax,ax            ;проверим знак числа
        jns init              ;SF=0? если да, то просто вывод числа
        mov cx,ax             ;иначе выводим как отрицательное, cx=ax
        mov ah,02h            ;выводим символ
        mov dl,'-'            ;поместим в dl символ минуса
        int 21h               ;выполним команду
        mov ax,cx             ;вернем старое значение, cx=ax
        neg ax                ;сменим знак операнда     
init:
        xor cx,cx             ;cx=0
        xor dx,dx             ;dx=0
    push -1               ;сохраним признак конца числа
    mov cx,10         ;делим на 10
repeat: 
        xor dx,dx         ;очистим регистр dx
    div cx                ;делим 
    push dx               ;сохраним цифру
    cmp ax,0          ;остался 0? (оптимальнее or ax,ax)
    jne repeat        ;нет -> продолжим
    mov ah,2h             ;вывод символа
digit:  
        pop dx                ;восстановим цифру
    cmp dx,-1         ;дошли до конца -> выход {оптимальнее: or dx,dx jl ex}
    je exit               ;завершить вывод
    add dl,'0'        ;преобразуем число в цифру
    int 21h               ;выведем цифру на экран
    jmp digit         ;и продолжим 
;------------------------------------------------
;Вывод ошибки о переполнении, при операции сложения
errorCF:
        mov ah,09h            ;команда на вывод строки
        mov dx,offset _errorCF;в dx смещение на строку
        int 21h               ;выполнить команду
        jmp exit              ;переход по метке
;------------------------------------------------
;Вывод ошибки о заеме, при операции вычитания       
errorOF:
        mov ah,09h            ;команда на вывод строки
        mov dx,offset _errorOF;в dx смещение на строку
        int 21h               ;выполнить команду
        jmp exit              ;переход по метке
;------------------------------------------------ 
exit:
        mov ax,4c00h       
        int 21h
code ends
end begin

1. Дизайн темы

Простой калькулятор

2. Дизайн контента

2.1 Основное содержание

  • 1. Напишите программу, которая может считывать данные с клавиатуры и выполнять вычисления сложения, вычитания, умножения и деления.
    2. Используйте прерывания BIOS и DOS для разработки калькулятора, который требует отображения главного меню на экране, предлагая пользователю ввести соответствующие цифровые клавиши для выполнения сложения, вычитания, умножения и деления соответственно Четыре функции расчета и функции для завершения программы.
    3. Используйте функцию вызова прерывания BIOS № 10, чтобы установить режим отображения. 4. Используйте подфункции 01 и 02, прерванные DOS, чтобы завершить прием с клавиатуры, и результаты отобразятся.

2.2 Основные функции
1. Показать функцию главного меню
Главное меню выглядит следующим образом:

Please input a number to choose the arithmetic operation
1—add  2—subtract  3—multiply 
4—divide  q—return to DOS

Во-вторых, функция расчета

  • 1. Не нажимайте цифровые клавиши «1», «2», «3», затем выполните соответствующие подмодули 1, 2, 3, сложите, вычтите и умножьте два байта и два байта и на экране Результат расчета отображается на экране.
    2. Нажмите цифровую клавишу «4», выполните подмодуль 4, выполните операцию деления, разделив два байта на один байт, и отобразите результат.
    3. Нажмите буквенную клавишу «q», чтобы запустить подмодуль 5, программа выйдет и вернется в DOS. 4. Если вы нажмете другие клавиши, появится строка приглашения: «Пожалуйста, нажмите
    № 1, 2, 3, 4, q: «, а затем продолжайте отображать главное меню.

3. Принципы и планы дизайна

3.1 Общий план
Используйте функцию вызова прерывания BIOS № 10, чтобы установить режим отображения.
Используйте прерванные подфункции DOS 01 и 02 для завершения принятия клавиатуры и отображения результатов.

Установите режим отображения экрана.

  MOV  AH,00H
         MOV AL, NUM (NUM - слово атрибута режима отображения)
    INT  10H

Затем примите значение ключа с клавиатуры и после различения перейдите к соответствующему субмодулю для расчета и отобразите результат.

MOV AH,01H
INT 21H; AL = ASCII-код набираемых символов

Получите строку команд с клавиатуры:

  MOV  AH,0AH
    MOV DX, OFFSET BUF; BUF - буфер данных, определенный в сегменте данных
    INT  21H

Показать односимвольную команду:

  MOV  AH,02H
    INT  21H

Команда для отображения строки строк:

   MOV  AH,09H
         MOV DX, OFFSET BUF; BUF - первый адрес отображаемой строки
    INT  21H

Процесс отображения десятичного числа:

  • Десятичное число отправляется в BX, BX делится на 1000, остаток отправляется в BX, частное находится в AL, диапазон составляет 0-9, и отображается содержимое в AL. Разделите BX на 100 и отправьте остаток в BX. Частное находится в AL. Содержимое в AL отображается и т. Д., Пока одна цифра не будет разделена.
    Если старший бит равен 0, он не должен отображаться, а самый старший 0 отображается. Вы можете определить, отображать ли 0, установив флаг и комбинируя частное.

3.2 Блок-схема программы
3.2.1 Основная программа


3.2.2 Подпрограмма расчета

3.3 Детальное проектирование системного модуля
Структурная схема, которая будет реализована в этом проекте, показана на рисунке 1.

(1) Настройки интерфейса
В основном реализует функцию определения стиля интерфейса, который отображается в виде меню.
(2) Настройки выбора алгоритма
используется для выбора сложения, вычитания, умножения и деления для расчета.
(3) Настройки преобразования цифровой системы
Применить алгоритм десятичного двоичного преобразования для обработки четырех операций сложения, вычитания, умножения и деления.

3.3.1 Настройки интерфейса
1. Установите режим отображения экрана:

   MOV AH,00H      
      MOV AL,2
      INT 10H
 Установите режим отображения экрана, вызвав функцию BOIS O2H. Здесь AL=2. Установите режим отображения экрана на 80 * 25 черно-белый текст.

2. Отобразите главное меню:
DOS предоставляет пользователям не только множество инструкций, но и сотни часто используемых подпрограмм, которые пользователи могут вызывать напрямую. Функции этих подпрограмм в основном предназначены для чтения / записи на диск, базового управления вводом / выводом и т. Д. При использовании пользователь должен знать только номер каждой подпрограммы, который становится номером вызова функции DOS. Процесс вызова:

  • (1) Функциональная панель DOS отправляется в регистр AH с номером. (2) При необходимости задайте входные параметры по мере необходимости (вводить параметры не нужно). (3) Написать инструкцию прерывания «INT
    21H”。

В конце вызова используйте его выходные параметры в соответствии с функцией.
Код отображения главного меню этой программы выглядит следующим образом:

MOV AH,09H                   
      MOV DX,OFFSET TOP
      INT 21H

Сначала определите строку символов, которая будет отображаться в главном меню в разделе данных, а затем завершите функцию, отобразив функцию 09H системы DOS, отображающую строку символов (конечный символ строки равен $, но она не отображается).

3.3.2 Настройки выбора алгоритма
Инструкциями сложения, вычитания, умножения и деления на языке ассемблера являются ADD, SUB, MUL и DIV.
Сначала выберите четыре операции, которые должны быть выполнены, которые предусматривают: если вы выбираете 1, вы добавляете; если вы выбираете 2, вы выполняете вычитание; когда вы выбираете 3, вы выполняете умножение; когда вы выбираете 4, вы выполняете деление. Конкретный процесс расчета анализируется следующим образом:

1. Получить номер с клавиатуры:

MOV AH,01H                    
    INT 21H
 Это реализуется путем вызова функции 01H системно набираемых символов DOS и нажатия клавиши Enter, параметром выхода является код ASCII введенных символов, который хранится в AL.

2. Получите номер, введенный для расчета:

MOV AH,0AH                   
    MOV DX,OFFSET BUF1
 Это достигается путем вызова функции 0AH системы DOS, которая вводит строку символов в буфер памяти. Параметры ввода следующие: DS: DX=Первый адрес буфера, в котором хранится набранная строка (DS: DX)=Пользовательский буфер; (DS: DX + 1)=Количество фактически введенных символов, введите введенную строку, начиная с (DS: DX + 2). Поэтому обратите внимание на первый адрес строки символов при использовании введенных чисел в следующей программе.

3. Показать результаты:

MOV AH,09H                   
MOV DX,OFFSET BUF3
    INT 21H
 Сохраните результат в буфере BUF3, определенном в разделе данных, а затем завершите функцию, отобразив функцию 09H системы DOS, отображающую строку символов (конечный символ строки равен $, но он не отображается).

3.3.3 Настройки преобразования цифровой системы
1. Подпрограмма A2-Преобразовать ASCII-код числа в двоичное число:

A2      PROC NEAR
                             MOV CL, BUF1 + 1; положить количество фактически набранных символов в BUF1 + 1 в CL
                             MOV CH, 0; CH очистить
                             MOV BX, OFFSET BUF1 + 2, введите адрес первой цифры в BX
                             PUSH BX; сохранить значение в BX
                             PUSH CX; сохранить значение в CX
L1:     MOV AL,[BX]
                             AND AL, 0FH; преобразовать ASCII-код в десятичную
              MOV [BX], AL; хранить десятичное число
                             INC BX; адрес следующей цифры
              LOOP L1
                             POP CX; восстановить CX
                             POP BX; восстановить BX
                             MOV AX, 0; добавить накопление и ноль
              MOV SI,10
L2:     MUL SI
              ADD AL,[BX]
              ADC AH,0
              INC BX
                             LOOP L2; кумулятивное умножение на 10
L3:     RET 
A2      ENDP

Вход: ASCII десятичного числа находится в BUF1; Выход: Преобразованное двоичное число находится в AX.
Алгоритм : сначала преобразуйте его в десятичную, а затем используйте метод накопления и умножения на 10 плюс X для преобразования в двоичную форму. Расчет (((0 * 10 + 1) * 10 + 6) * 10 + 3) приводит к двоичному числу.

2. Преобразуйте двоичное число результата вычисления в соответствующий десятичный код ASCII и выведите на экран (блок A3):
Для разработки программы этого модуля, пожалуйста, обратитесь к блокам A3 и BB в списке программ и описании
Запись: двоичное число находится в AX; Выход: преобразованный код ASCII находится в BUF.
Алгоритм : диапазон чисел в AX составляет от +32767 до -32768, сначала проверьте знаковый бит AX, решите вывести ‘+’ ‘-‘, если он отрицательный, сначала найдите дополнение После того, как оригинальный код может быть обработан с положительным числом. Метод: Разделите двоичное число, которое нужно сначала преобразовать, на 10000, частное — 10 000 цифр, затем разделите остаток на 1000, частное — это число тысяч, и так далее, найдите сто десять цифр, а остальные — те, которые являются цифрами. число. Наконец, добавьте 30H к каждому числу, которое является соответствующим символом.

4. Соберите исходный код

DATA   SEGMENT
        N=15
TOP     DB 5   DUP(0AH)
    DB N   DUP(' '),' Please input a number to choose the arithmetic operation', 0DH,0AH
        DB N   DUP(' '),'             ', 0DH,0AH
    DB N   DUP(' '),'1—add  2—subtract  3—multiply', 0DH,0AH
    DB N   DUP(' '),'4—divide  q—return to DOS', 0DH,0AH
        DB N   DUP(' '),'             ', 0DH,0AH
        DB N   DUP(' '),'Choice (1.2.3.4.q):$'
MESG1   DB 0DH,0AH,'Please press number: 1.2.3.4.q !$' 
MESG2   DB 0DH,0AH,'Please input the first number  :$'
MESG3   DB 0DH,0AH,'Please input the second number :$'
MESG4   DB 0DH,0AH,'Play Add :$'
MESG5   DB 0DH,0AH,'Play Subtract :$'
MESG6   DB 0DH,0AH,'Play Multiply :$'
MESG7   DB 0DH,0AH,'Play Divide :$'
MESG8   DB 0DH,0AH,'The Result :$'
MESG9   DB 0DH,0AH,'PRESS Q RETURN DOC,ANY PRESS RETURN START!$'
BUF1    DB  6                   ;  Десятичный буфер, может принимать 6 символов
        DB  ?                   ;    Зарезервировано, используется для фактического количества символов, введенных в DOC при вызове 10-го числа.
        DB  6 DUP(?)            ;    Один символьный бит, четырехзначный код ASCII плюс возврат каретки
BUF2    DW ?                    ;  После преобразования первого числа в двоичное, поместите его здесь
BUF3    DB 6 DUP(?),'$'         ;  После преобразования результата в код ASCII, поместите его здесь
DATA    ENDS
STACK   SEGMENT STACK'STACK'
CODE    SEGMENT
        ASSUME  CS:CODE , DS:DATA
START:  MOV AX,DATA
        MOV DS,AX
        MOV AH,00H                    ;  Установите метод отображения экрана
        MOV AL,2
        INT 10H
        MOV AH,09H                    ;  Показать меню
        MOV DX,OFFSET TOP
        INT 21H
AA:     MOV AH,01H                    ;  Получить номер с клавиатуры
        INT 21H
        CMP AL,'1'                    ;  По сравнению с 1
        JNE NEXT1                     ;  Если оно не равно 1, перейдите к следующему
        JMP JIA                       ;  Если оно равно 1, перейти к процессу сложения
NEXT1:  CMP AL,'2'                    ;  По сравнению с 2
        JNE NEXT2                     ;  Если оно не равно 2, перейдите к следующему
        JMP JIAN                      ;  Равное 2 пойдет в процесс вычитания
NEXT2:  CMP AL,'3'                    ;  По сравнению с 3
        JNE NEXT3                     ;  Не равно 3, перейти к следующему
        JMP CHENG                     ;  Перейти к программе умножения, когда она равна 3
NEXT3:  CMP AL,'4'                    ;  По сравнению с 4
        JNE NEXT4                     ;  Если оно не равно 4, перейдите к NEXT4
        JMP CHU                       ;  Равный 4 и перейти к процедуре деления
NEXT4:  CMP AL,'q'                    ;  По сравнению с q
        JNE NEXT5                     ;  Если оно не равно q, переходите к NEXT5
        MOV AH,4CH                    ;  Вернуться к DOS
        INT 21H
NEXT5:  MOV AH,09H                    ;  Дисплей MESG1
        MOV DX,OFFSET MESG1
        INT 21H
        JMP AA                        ;  Перейти к аа
A1      PROC NEAR
        MOV AH,09H                    ;  Подскажите по первому номеру
        MOV DX,OFFSET MESG2
        INT 21H
        MOV AH,0AH                    ;  Получить первый номер
        MOV DX,OFFSET BUF1
        INT 21H
        CALL A2                       ;  Вызов A2 для двоичного преобразования ASCII
        MOV BUF2,AX                   ;  Поместите первое преобразованное двоичное число в BUF2
        MOV AH,09H                    ;  Подскажите второй номер
        MOV DX,OFFSET MESG3
        INT 21H
        MOV AH,0AH                    ;  Получить второй номер
        MOV DX,OFFSET BUF1
        INT 21H
        CALL A2                       ;  Вызовите А2, чтобы преобразовать второе число в двоичный код ASCII
        RET
A1      ENDP
A2      PROC NEAR
        MOV CL,BUF1+1                 ;  Поместите количество фактически введенных символов в BUF1 + 1 в CL
        MOV CH,0                      ;  Сброс CH
        MOV BX,OFFSET BUF1+2          ;  Введите адрес первой цифры в BX
        PUSH BX                       ;  Сохранить значение в BX
        PUSH CX                       ;  Сохранить значение в CX
L1:     MOV AL,[BX]
        AND AL,0FH                    ;  Преобразовать ASCII-код в десятичную
        MOV [BX],AL                   ;  Хранить десятичные
        INC BX                        ;  Адрес следующей цифры
        LOOP L1
        POP CX                        ;  Восстановить CX
        POP BX                        ;  Восстановить BX
        MOV AX,0                      ;  Добавить и ноль
        MOV SI,10
L2:     MUL SI
        ADD AL,[BX]
        ADC AH,0
        INC BX
        LOOP L2                       ;  Суммарное умножение на 10
L3:     RET 
A2      ENDP
JIA:    MOV AH,09H                    ;  Процедура добавления
        MOV DX,OFFSET MESG4
        INT 21H
        CALL A1                       ;  Звоните А1
        ADD AX,BUF2                   ;  Поместите преобразованное первое число в BUF2 и поместите его в AX 
        JMP A3                        ;      Добавьте второе число
JIAN:   MOV AH,09H                    ;  Процедура вычитания
        MOV DX,OFFSET MESG5
        INT 21H
        CALL A1
        MOV BX,AX                     ;  Поместите преобразованный второй номер в AX в BX
        MOV AX,BUF2                   ;  Поместите первый номер в BUF2 после преобразования в AX
        SBB AX,BX                     ;  Вычтите два числа
        JMP A3
CHENG:  MOV AH,09H                    ;  Программа умножения
        MOV DX,OFFSET MESG6
        INT 21H
        CALL A1
        MOV BX,AX                     ;  Поместите преобразованный второй номер в AX в BX
        MOV AX,BUF2                   ;  Поместите первое число в BUF2 после преобразования в AX
        MUL BX                        ;  Умножьте два числа
        JMP A3
CHU:    MOV AH,09H                    ;  Процедура деления
        MOV DX,OFFSET MESG7
        INT 21H
        CALL A1
        MOV DX,AX                    ;  Поместите преобразованный второй номер в AX в DX
        MOV AX,BUF2                  ;  Поместите первое число в BUF2 после преобразования в AX
        DIV DL                       ;  Разделите два числа, частное в AL
        MOV AH,0H                    ;  Очистить AH
        JMP A3
A3:     MOV BUF3,'+'                 ;  Поставить BUF3'+'
        CMP AX,0                     ;  Сравните результат с 0
        JGE L4                       ; AX>0 Перейти к L4
        NEG AX                       ; AX<0 инвертировать AX
        MOV BUF3,'-'                 ;  Поставить BUF3'-'
L4:     CWD
        MOV BX,10000
        DIV BX                       ;  Частное является первой цифрой в AL, а остальная часть находится в DX
        CALL BB
        MOV BUF3+1,AL                ;  Поместите первый номер в единицу BUF3 + 1
        MOV AX,DX                    ;  Положите остаток в DX в AX
        CWD
        MOV BX,1000
        DIV BX                       ;  Частное является второй цифрой в AL, а остаток в DX
        CALL BB
        MOV BUF3+2,AL                ;  Поместите второе число в единицу BUF3 + 2
        MOV AX,DX                    ;  Положите остаток в DX в AX
        MOV BL,100
        DIV BL                       ;  Частное является третьей цифрой в AL, а остаток в AH
        CALL BB
        MOV BUF3+3,AL                ;  Поместите третий номер в блок BUF3 + 3
        MOV AL,AH                    ;  Поместите остаток в AH в AL
        CBW
        MOV BL,10
        DIV BL                       ;  Частное является четвертой цифрой в AL, а остаток в A
        CALL BB
        MOV BUF3+4,AL                ;  Поместите четвертое число в единицу BUF3 + 4
        ADD AH,30H                   ;  Преобразовать свою цифру в код ASSCII
        MOV BUF3+5,AH                ;  Поместите пятое число в блок BUF3 + 5
        MOV AH,09H                   ;  Быстрый результат
        MOV DX,OFFSET MESG8
        INT 21H
        MOV AH,09H
        MOV DX,OFFSET BUF3            ;  Показать результаты в буфере BUF3
        INT 21H
        MOV AH,09H                    ;  Подскажите стоит ли возвращаться в DOS или пересчитать
        MOV DX,OFFSET TOP
        INT 21H
        JMP NEXT5                     ;  Пересчет других цифр
BB      PROC NEAR
        CMP AL,0H                     ;  Сравнить с нулем
        JE L5
        JMP L6
L5:     MOV AL,20H                    ;  Равный нулю, назначьте AL пробел
        JMP L7
L6:     ADD AL,30H                    ;  Не равно нулю, преобразовать число в код ASSCII
L7:     RET
BB      ENDP
A4:     MOV AH,4CH                    ;  AL равняется'Q'Просто вернитесь в DOS
        INT 21H
CODE    ENDS
        END START

5. Экспериментальные результаты

прибавление

Вычитание

Умножение

Фильтр
Калькулятор
Усовершенствованный калькулятор
Резюме

В предыдущей теме была рассмотрена программа для ввода шестнадцатеричных чисел. Но эта программа обладала существенным недостатком: кроме чисел 0 … F она воспринимала практически все вводимые символы, и интерпретировала их как числа.

Поэтому, нам следует написать процедуру, которая при вводе будет фильтровать символы шестнадцатеричных чисел: «0» … «9», «A» … «F».

Фильтр

Рассмотрим две новые инструкции, которые потребуются для фильтрации чисел:

JA («Jump if Above» — перейти если больше)
JB («Jump if Below» — перейти если меньше)

Инструкции JA и JB работают только с положительными числами 0 … FFFFh, и игнорируют флаг переполнения (OF). Изученная ранее инструкция JL (перейти, если меньше), работает как с положительными, так и отрицательными числами (проверяет OF).

Если для запроса символа использовать функцию 01h прерывания INT 21h, то ввод любого символа будет сопровождаться эхом (т.е. выводом данного символа на экран).

Новая процедура не должна отображать ошибочно введенные символы. Для ввода символа без эхо-повтора, мы используем функцию 08h прерывания INT 21h.

Ниже приводится программа-тест для проверки фильтра. Для повышения читабельности, из листинга удалены коды инструкций и сегмент адреса. Инструкции основной программы внесите по адресам 100h и 103h, а процедуру расположите по адресу 200h:

0100 CALL 0200     основная программа
0103 INT  20
0200 PUSH DX       процедура ввода и фильтрации чисел 0 ... F
0201 MOV  AH,08    ввод символа без эха
0203 INT  21
0205 CMP  AL,30
0207 JB   0203     если AL < 30h, то переход на 203h
0209 CMP  AL,46
020B JA   0203     если AL > 46h, то переход на 203h
020D CMP  AL,3A
020F JB   0215     если AL < 3Ah, то переход на 215h
0211 CMP  AL,41
0213 JB   0203     если AL < 41h, то переход на 203h
0215 MOV  AH,02
0217 MOV  DL,AL    печать отфильтрованного символа
0219 INT  21
021B SUB  AL,30
021D CMP  AL,09    преобразование кода символа в число
021F JLE  0223
0221 SUB  AL,07
0223 POP  DX
0224 RET

Протестируйте программу с различными символами и убедитесь, что фильтр пропускает только символы шестнадцатеричных чисел: «0» … «F». Проверьте ввод символов «a» … «f».

Почему процедура начинается с адреса 200h? Процедуры могут располагаться в любом месте программы, но обычно их пишут после основной программы. Адрес 200h выбран с учетом места под текст основной программы (100h — ячеек). Можно перенести процедуру на адрес 105h, тогда между основной программой и процедурой не останется свободных ячеек. При этом придется поменять все адреса в инструкциях условных переходов.

В процедуре используются регистры AX и DX, но в стеке сохраняется только регистр DX. Почему? В ходе выполнения процедуры, в AL передается цифра введенная с клавиатуры. Если в начале процедуры сохранить значение регистра AX в стек, а в конце процедуры его восстановить, то введенная внутри процедуры цифра будет потеряна. Поэтому, не каждый регистр можно временно сохранять в стеке.

Данную процедуру можно использовать для ввода двузначных чисел. Например, программа должна запрашивать hex-код символа, и выводить этот символ на экран:

0100 CALL 0200     переход на процедуру ввода hex-цифры
0103 MOV  DL,AL
0105 MOV  CL,04
0107 SHL  DL,CL
0109 CALL 0200     переход на процедуру ввода hex-цифры
010C ADD  DL,AL
010E MOV  AH,02
0110 INT  21
0112 INT  20

Проверьте работу программы с кодами:
2Ah (символ «*»), 07h (звуковой сигнал), 40h (символ «@») и др.

Калькулятор

Программа запрашивает две hex-цифры, суммирует их и выводит результат на экран.
Например: F + 1 = 10

0100 CALL 0200
0103 MOV  BL,AL
0105 MOV  AH,02
0107 MOV  DL,2B    печать знака "+"
0109 INT  21
010B CALL 0200
0110 MOV  AH,02
0112 MOV  DL,3D    печать знака "="
0114 INT  21
010E ADD  BL,AL
0116 CALL 0150
0119 INT  20
...
0150 Процедура печати двузначного числа из регистра BL
...
0200 Процедура ввода hex-цифры в регистр AL

Программу печати двузначного числа из регистра BL нужно переделать в процедуру:

  1. замените INT 20h на RET;
  2. измените адреса в инструкциях JL;
  3. можете добавить инструкции PUSH и POP (но в данном случае это не обязательно).

Задачи:

  1. Если результат сложения меньше 10h, то в старшем разряде ответа печатается ноль. Например: 3 + 2 = 05. Вам нужно убрать этот «лишний» ноль.
  2. Убрать пустые адресные пространства между основной программой и процедурами. Записать программу на диск.

Усовершенствованный калькулятор

Вы написали программу, которая умеет складывать два шестнадцатеричных числа.
Теперь необходимо «обучить» калькулятор вычитанию.

В приложениях находится текст готовой программы. Но не спешите искать готовое решение. Эта программа является итоговой задачей, желательно написать ее самостоятельно. Если вы успешно справитесь с ней, то знайте: время, проведенное за клавиатурой, потрачено не зря.

Для написания программы можно использовать следующую блок-схему:

Ввод-фильтр первой цифры,
копия числа в регистре BH.
Ввод-фильтр знака «+» или «-«,
копия знака в регистре DH.
Ввод-фильтр второй цифры,
копия числа в регистре BL.
Ввод-фильтр знака «=».
После ввода «=» появляется ответ.
Вычисление результата:
если DH = «+», то BH + BL
если DH = «-«, то BH — BL
Вывод результата на экран.
INT 20h
Процедура:
«Вывод символа на экран»
Процедура:
«Ввод-фильтр hex-цифры»

Эта блок-схема не является единственным решением, вы можете разработать свой вариант алгоритма.

Программу можно подготовить в более простом виде: без контроля отрицательных чисел и проверки разрядности числа. Например:

1 - 2 = FF
2 + 3 = 05

В усложненном варианте результат выглядит иначе:

1 - 2 = -1
2 + 3 = 5

Именно так формирует результат программа из приложения.

Для написания программы вам может потребоваться инструкция безусловного перехода:
JMP (Jump — переход). Например:

JMP  210     переход на адрес 210h

Резюме

Седьмая тема завершает вводную часть курса «Основы программирования на ассемблере». Теперь вы знаете, что:

  • числа можно представлять не только в десятичной системе счисления;
  • арифметические операции выполняются в любой системе счисления;
  • регистры позволяют хранить в микропроцессоре одновременно несколько чисел;
  • система команд CPU позволяет выполнять арифметические и другие действия;
  • процедуры и прерывания значительно упрощают разработку программ.

Многое из того, что вы написали, будет использовано далее. Во второй части курса мы перейдем к более серьезным и интересным вопросам программирования на ассемблере.

This program is a programmable big hexadecimal number calculator, written in x86 assembly language using FASM (the flat assembler). It works with unsigned integers that can be up to 400 bytes (800 hex digits) in length. It looks like this:

Up to 14 parameters are available to the user of the program, all of which are 400 byte hex numbers. Ten of these parameters can be defined by the user, while the other 4 are built-in.

The edit control on the left allows the user to enter up to ten 400 byte input parameters. Curly brackets are used to define the name of a parameter. A parameter name can be up to 8 characters in length and can include letters, numbers and an underscore. It is case sensitive. The parameter name is followed by a hex value which can be up to 400 bytes (or 800 hex digits) in length. If nothing follows a parameter name definition, the value of that parameter will be initialised to zero. For example:

Once the parameters and their values have been entered, press the [Load Parameters] button to read them into memory. To clear all user defined parameters, press the [Clear Parameters] button.

There are also 4 built-in parameters: reg_1, reg_2, reg_3 and reg_4. These are displayed in the edit control on the lower right.

Calculations are implemented by entering instructions into the edit control on the upper right. Up to 40 instructions can be entered and executed at any one time. Instructions are executed by pressing the [Run Instructions] button. Press the [Clear Instructions] button to clear the instructions edit control.

Press the [Reset All] button to clear everything.

The instructions used to perform arithmetic operations are like x86 assembly language instructions. But instead of the arguments being registers or memory locations, they are the 400 byte hex number parameters as described above. Some of the instructions also take an immediate value as one of their arguments. Depending on the instruction, the immediate value will be read either as a hex number or a decimal number. The hex number immediate values can be up to 16 bytes (32 hex digits) in length and must not contain any white space or non hex digit characters. The decimal number immediate values can only be up to 4 decimal digits in length and must not be greater than 3200.

The instruction are not case sensitive. So for example, mov can be written as: mov, Mov or MOV. The arguments (the parameter names) are case sensitive.

The instructions and their arguments are white space delimited, and there can also be any amount of leading and trailing white spaces.

In other words: move some immediate hex values into reg_1, reg_2 and reg_3. Then add reg_1 to reg_2. Not reg_1 and then multiply reg_3 by reg_1. Xor reg_2 into reg_3 and then right rotate reg_2 by 127 bits.

Dest is one of the 400 byte hex values.

Both dest and src are one of the 400 byte hex values.

Dest is one of the 400 byte hex values. The second argument is a 16 byte (128 bit) hex value. There must be no spaces or non hex digits in this argument.

Dest is one of the 400 byte hex values. The second argument is a decimal number of up to 4 digits. It cannot be greater than 3200.

The argument is an immediate value which is read as a decimal number of up to 4 digits. It cannot be greater than 3200.

The Nbytes instruction affects the SHL, SHR, ROTL and ROTR instructions. It sets the word size that these instructions operate on. For example Nbytes 4 sets the word size to 4 bytes, and any parameter which is being shifted or rotated will be treated as a 4 byte value by these operations.

Nbytes must be in the range 1 – 400. Any value outside of this range will cause the nBytes value to be set to the default of 400.

Here are some examples showing the sort of calculations that can be done with the program.

To find the greatest common divisor (GCD) of two numbers A and B, the Euclidean Algorithm repeatedly applies the identity:

– until A mod B becomes zero.

To use the calculator to find the GCD of a pair of numbers, first enter the numbers (for example):

Repeatedly press the [Run Instructions] button to execute these two instructions again and again until B has been reduced to zero. At this point, A will hold the value of the GCD (which in this case is 1).

From the Advanced Encryption Standard, the finite field multiplication function xtime():

These instructions fill an array with the byte values 0 – FF, and a second array with the byte values after finite field multiplication by 2.

To encrypt using the above test vector, enter the following parameters and press [Load Parameters]:

Note the first instruction: nbytes 4. This instruction tells the program that when the SHL and SHR operations are applied, the parameters being shifted are to be treated as 4 byte values (as required by the TEA algorithm).

Executing these instructions should give the result: v0 = 8df5d71f and v1 = 9c3be43f. Note that at the end of the calculation v0 and v1 are truncated to 32 bit (4 byte) values.

Use an adaptation of the middle square method to generate a pseudo random 400 byte value:

Use the Linear Congruential Generator to generate a pseudo random 400 byte value:

The modulus value m (0x100000000) in this case is just 232, which is equivalent to truncating to 32 bits.

Use the Galois Linear Feedback Shift Register to generate a pseudo random 400 byte value:

Enter instructions to do the modular exponentiation calculation. Note that each of A and C should not be greater than 200 bytes.

Note that for the larger input numbers, it can take a couple of minutes for this calculation to execute.

To grab this code, select it with the mouse and copy it. It can then be pasted directly into the FASM IDE.

; -------------------------------------------------------------------------------------

 format PE GUI 4.0

 entry start

 include 'win32a.inc'

; -------------------------------------------------------------------------------------

 IDD_THE_DIALOG = 102

 IDC_INPUT = 1000
 IDC_CALCS = 1001
 IDC_OUTPUT = 1002

 IDC_BTN_LOAD_DATA = 1003
 IDC_BTN_CALC = 1004
 IDC_BTN_CLR_DATA = 1005
 IDC_BTN_CLR_INSTR = 1006
 IDC_BTN_RESET_ALL = 1007

; -------------------------------------------------------------------------------------

 HEX_LEN = 400

 MAX_HEX_DIGITS = 2*HEX_LEN

 N_CALCS = 40

 CALCS_BUFFER_SZ = 20*N_CALCS

; -------------------------------------------------------------------------------------

 section '.code' code readable executable

  start:

    invoke GetModuleHandle,0
    invoke DialogBoxParam,eax,IDD_THE_DIALOG,0,DialogProc,0

  exit:

    invoke  ExitProcess,0 

; -------------------------------------------------------------------------------------

proc DialogProc uses esi edi ebx,hwnddlg,msg,wparam,lparam

	cmp [msg],WM_INITDIALOG
	je .wminitdialog

	cmp [msg],WM_COMMAND
	je .wmcommand

	cmp [msg],WM_CLOSE
	je .wmclose

	xor eax,eax
	jmp .quit

  .wminitdialog:

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,szInputText
	invoke SetDlgItemText,[hwnddlg],IDC_CALCS,szExprText

	stdcall ClearRegisterValues

	stdcall PrintRegisterValues

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .wmcommand:

	cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_LOAD_DATA
	je .LOAD

	cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CALC
	je .CALC

	cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CLR_DATA
	je .CLR_DATA

	cmp [wparam], BN_CLICKED shl 16 + IDC_BTN_CLR_INSTR
	je .CLR_INSTR

	cmp  [wparam], BN_CLICKED shl 16 + IDC_BTN_RESET_ALL
	je .RESET

    jmp .done

  .LOAD:

	invoke GetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay,12000

	stdcall ValidateParamsBuffer

	cmp al,0xff

	je .ERR_8

	stdcall ProcessDisplayBuffer

	stdcall PrintSymbolsParams

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay

	stdcall ClearRegisterValues

	stdcall PrintRegisterValues

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .ERR_8:

	mov [nErrorCode],byte 8

	jmp .ERROR

  .CALC:

	invoke GetDlgItemText,[hwnddlg],IDC_CALCS,bfDisplay,12000

	stdcall ValidateCalcsBuffer

	cmp al,0xff

	je .ERROR

	stdcall LoadCalculations

	cmp al,0xff

	je .ERROR

	stdcall ExecuteCalculations

	cmp eax,0xffffffff

	je .CALC_ERROR

	stdcall PrintSymbolsParams

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay

	stdcall PrintRegisterValues

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .CALC_ERROR:

	stdcall PrintExeError

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	stdcall PrintSymbolsParams

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,bfDisplay

	jmp .done

  .ERROR:

	stdcall PrintErrorMessage

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .CLR_DATA:

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,""

	stdcall ClearSymbolTable

	stdcall ClearRegisterValues

	stdcall PrintRegisterValues

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .CLR_INSTR:

	invoke SetDlgItemText,[hwnddlg],IDC_CALCS,""

	jmp .done

  .RESET:

	invoke SetDlgItemText,[hwnddlg],IDC_INPUT,szInputText
	invoke SetDlgItemText,[hwnddlg],IDC_CALCS,szExprText

	mov [nBytes],HEX_LEN

	stdcall ClearSymbolTable

	stdcall ClearRegisterValues

	stdcall PrintRegisterValues

	invoke SetDlgItemText,[hwnddlg],IDC_OUTPUT,bfDisplay

	jmp .done

  .wmclose:

	invoke EndDialog,[hwnddlg],0

  .done:

	mov eax,1

  .quit:

	ret

endp

; -------------------------------------------------------------------------------------

proc ClearRegisterValues

	lea edi,[Reg_1]

	stdcall ClearParam

	lea edi,[Reg_2]

	stdcall ClearParam

	lea edi,[Reg_3]

	stdcall ClearParam

	lea edi,[Reg_4]

	stdcall ClearParam

	ret

endp

; -------------------------------------------------------------------------------------

macro PRINT_CRLF
{
  mov [edi],byte 13
  inc edi

  mov [edi],byte 10
  inc edi
}

; -------------------------------------------------------------------------------------

proc PrintRegisterValues

  ; print reg_1, reg_2, reg_3, reg_4

	lea edi,[bfDisplay]

	; reg_1

	mov [edi],dword "reg_"
	add edi,4

	mov [edi],dword "1=  "
	add edi,4

	PRINT_CRLF
	PRINT_CRLF

	lea esi,[Reg_1]

	stdcall PrintHexValue

	; reg_2

	mov [edi],dword "reg_"
	add edi,4

	mov [edi],dword "2=  "
	add edi,4

	PRINT_CRLF
	PRINT_CRLF

	lea esi,[Reg_2]

	stdcall PrintHexValue

	; reg_3

	mov [edi],dword "reg_"
	add edi,4

	mov [edi],dword "3=  "
	add edi,4

	PRINT_CRLF
	PRINT_CRLF

	lea esi,[Reg_3]

	stdcall PrintHexValue

	; reg_4

	mov [edi],dword "reg_"
	add edi,4

	mov [edi],dword "4=  "
	add edi,4

	PRINT_CRLF
	PRINT_CRLF

	lea esi,[Reg_4]

	stdcall PrintHexValue

	; null terminate

	mov [edi],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc PrintSymbolsParams

  ; print the parameter names and their values to the bfDisplay buffer

	lea edi,[bfDisplay]

	; Symbol_1 and Param_1

	lea esi,[Symbol_1]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_1]

	stdcall PrintHexValue

	; Symbol_2 and Param_2

	lea esi,[Symbol_2]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_2]

	stdcall PrintHexValue

	; Symbol_3 and Param_3

	lea esi,[Symbol_3]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_3]

	stdcall PrintHexValue

	; Symbol_4 and Param_4

	lea esi,[Symbol_4]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_4]

	stdcall PrintHexValue

	; Symbol_5 and Param_5

	lea esi,[Symbol_5]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_5]

	stdcall PrintHexValue

	; Symbol_6 and Param_6

	lea esi,[Symbol_6]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_6]

	stdcall PrintHexValue

	; Symbol_7 and Param_7

	lea esi,[Symbol_7]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_7]

	stdcall PrintHexValue

	; Symbol_8 and Param_8

	lea esi,[Symbol_8]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_8]

	stdcall PrintHexValue

	; Symbol_9 and Param_9

	lea esi,[Symbol_9]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_9]

	stdcall PrintHexValue

	; Symbol_10 and Param_10

	lea esi,[Symbol_10]

	cmp [esi],byte 0

	je .DONE

	stdcall PrintSymbol

	lea esi,[Param_10]

	stdcall PrintHexValue

  .DONE:

	; null terminate

	mov [edi],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc PrintHexValue uses ecx eax

  ; ESI and EDI set outside of this procedure

	locals

	  count db 0

	endl

	; find the first non zero dword in the hex value

	; starting from the high dword

	mov cx,HEX_LEN-4

	add esi,HEX_LEN-4

  .FIND:

	mov eax,[esi]

	cmp eax,0

	jne .PRINT

	sub esi,4

	sub cx,4

	jcxz .PRINT

	jmp .FIND

  .PRINT:

	mov eax,[esi]

	; dword to 8 digits

	stdcall DWordToStr

	jcxz .DONE

	sub cx,4

	sub esi,4

	inc [count]

	; add a CRLF to the output string after every 6 dwords

	cmp [count],byte 6

	je .ADD_CRLF

	jmp .PRINT

  .ADD_CRLF:

	mov [count],byte 0

	PRINT_CRLF

	jmp .PRINT

  .DONE:

	PRINT_CRLF
	PRINT_CRLF

	ret

endp

; -------------------------------------------------------------------------------------

proc WordToStr uses edx

  ; print a word (2 bytes) to a string

  ; word passed in AX
  ; EDI set outside of this function

	mov dx,ax
	mov al,ah

	stdcall ByteToDigits

	mov [edi],ah
	inc edi

	mov [edi],al
	inc edi

	mov al,dl

	stdcall ByteToDigits

	mov [edi],ah
	inc edi

	mov [edi],al
	inc edi

	ret

endp

; -------------------------------------------------------------------------------------

proc DWordToStr

  ; print a dword (4 bytes) to a string

  ; word passed in EAX
  ; EDI set outside of this function

	locals

	  tmp dw 0

	endl

	mov word [tmp],ax

	shr eax,16

	stdcall WordToStr

	mov ax,word [tmp]

	stdcall WordToStr

	; add a space

	mov [edi],byte 32
	inc edi

	ret

endp

; -------------------------------------------------------------------------------------

proc PrintSymbol uses ecx

  ; ESI and EDI set outside of this procedure

	mov cx,0

  .PRINT:

	mov al,[esi]

	cmp al,0

	je .DONE

	mov [edi],al

	inc esi
	inc edi

	inc cx

	cmp cx,8

	jge .DONE

	jmp .PRINT

  .DONE:

	; '='

	mov [edi],byte 61
	inc edi

	PRINT_CRLF
	PRINT_CRLF

	ret

endp

; -------------------------------------------------------------------------------------

proc ProcessDisplayBuffer

  ; extract the symbols and hex values from bfDisplay

	stdcall ClearSymbolTable

	lea esi,[bfDisplay]

	; get Symbol_1 and Param_1

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_1]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_1]

	stdcall InputBufferToParam

	; get Symbol_2 and Param_2

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_2]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_2]

	stdcall InputBufferToParam

	; get Symbol_3 and Param_3

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_3]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_3]

	stdcall InputBufferToParam

	; get Symbol_4 and Param_4

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_4]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_4]

	stdcall InputBufferToParam

	; get Symbol_5 and Param_5

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_5]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_5]

	stdcall InputBufferToParam

	; get Symbol_6 and Param_6

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_6]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_6]

	stdcall InputBufferToParam

	; get Symbol_7 and Param_7

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_7]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_7]

	stdcall InputBufferToParam

	; get Symbol_8 and Param_8

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_8]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_8]

	stdcall InputBufferToParam

	; get Symbol_9 and Param_9

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_9]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_9]

	stdcall InputBufferToParam

	; get Symbol_10 and Param_10

	stdcall FindSymbol

	cmp al,0

	je .DONE

	lea edi,[Symbol_10]

	stdcall GetSymbol

	stdcall HexStrToInputBuffer

	lea edi,[Param_10]

	stdcall InputBufferToParam

 .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc FindSymbol

  ; look for the opening brace { (ascii = 123) in bfDisplay

  ; ESI is set outside of this procedure

  ; set AL to indicate success

	mov al,0

  .FIND:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	cmp dl,123

	je .FOUND

	inc esi

	jmp .FIND

  .FOUND:

	mov al,1

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc GetSymbol uses ecx

  ; store the symbol in the symbol table

  ; delimiters = { and } = 123 and 125

  ; ESI and EDI set outside of this procedure

	xor ecx,ecx

	; ESI already points to the '{'

	inc esi

  .STORE:

	mov al,[esi]

	cmp al,0

	je .DONE

	; '}'

	cmp al,125

	je .DONE

	mov [edi],al

	inc esi
	inc edi

	inc cx

	; read up to 8 chars

	cmp cx,8

	jge .CLOSE

	jmp .STORE

  .CLOSE:

	; if 8 chars were read, find the closing brace

	mov al,[esi]

	cmp al,0

	je .DONE

	cmp al,125

	je .DONE

	inc esi

	jmp .CLOSE

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ClearSymbolTable uses edi ecx

  ; clear the symbols

	lea edi,[Symbol_1]

	mov cx,0

  .CLEAR:

	mov [edi],byte 0

	inc edi
	inc cx

	cmp cx,100

	jl .CLEAR

	ret

endp

; -------------------------------------------------------------------------------------

proc InputBufferToParam uses esi ecx

  ; convert the string in InputBuffer to a hex value

  ; EDI is set outside of this procedure

  ; EDI points to one of: Param_1, Param_2, etc

	lea esi,[InputBuffer]

	; clear the buffer pointed to by EDI

	mov edx,edi

	stdcall ClearParam

	; restore EDI

	mov edi,edx

	; count the digits in InputBuffer and save to AX

	stdcall CountDigits

	; check if the input buffer is empty

	cmp ax,0

	je .DONE

	; calculate the offset in bytes from the start of EDI

	; based on the number of digits in the input buffer (given by AX)

	xor ecx,ecx

	mov cx,ax

	shr cx,1

	test ax,1

	jnz .ODD

  .EVEN:

	; the input buffer contains an even number of hex digits

	; offset = (nDigits/2) - 1

	dec cx

	add edi,ecx

	jmp .GET_DIGITS

  .ODD:

	; the input buffer contains an odd number of hex digits

	; offset = nDigits/2

	; convert the first digit

	add edi,ecx

	mov al,[esi]

	stdcall HexDigitToValue

	mov [edi],al

	inc esi

	dec edi

	dec cx

	cmp cx,0

	jl .DONE

  .GET_DIGITS:

	; convert 2 hex digits to 1 byte

	mov al,[esi]

	stdcall HexDigitToValue

	shl al,4

	mov [edi],al

	inc esi

	mov al,[esi]

	stdcall HexDigitToValue

	or [edi],al

	inc esi

	dec cx

	cmp cx,0

	jl .DONE

	dec edi

	jmp .GET_DIGITS

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc HexStrToInputBuffer uses edi ecx

  ; copy a string containing a hex number to the InputBuffer

  ; filter out any non hex digit characters

  ; ESI is set outside of this procedure

	lea edi,[InputBuffer]

	xor ecx,ecx

  .FILTER:

	mov al,[esi]

	; 0 = null terminator

	cmp al,0

	je .DONE

	; 123 = {

	cmp al,123

	je .DONE

	cmp al,48

	jl .NOT_HEX

	cmp al,57

	jle .COPY

	cmp al,65

	jl .NOT_HEX

	cmp al,70

	jle .COPY

	cmp al,97

	jl .NOT_HEX

	cmp al,102

	jg .NOT_HEX

  .COPY:

	mov [edi],al

	inc edi

	inc ecx

	cmp ecx,MAX_HEX_DIGITS

	jge .DONE

  .NOT_HEX:

	inc esi

	jmp .FILTER

  .DONE:

	; null terminate the new string

	mov [edi],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc ClearParam uses ecx

  ; clear the input parameter

  ; EDI is set outside of this procedure

	xor ecx,ecx

  .CLEAR:

	mov [edi],byte 0

	inc edi
	inc cx

	cmp cx,HEX_LEN

	jl .CLEAR

	ret

endp

; -------------------------------------------------------------------------------------

proc CountDigits uses esi ecx

  ; count the number of hex digits in the InputBuffer string

	mov ax,0

	xor ecx,ecx

	lea esi,[InputBuffer]

  .COUNT:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	inc cx

	inc esi

	jmp .COUNT

  .DONE:

	; return the digit count in AX

	mov ax,cx

	ret

endp

; -------------------------------------------------------------------------------------

proc HexDigitToValue

  ; convert a hex digit to a 4 bit value

  ; input in AL - output in AL

	; is AL in the range: 48 - 57 (0 - 9) ?

	cmp al,48

	jl .NOT_HEX

	cmp al,57

	jg .A_Z

	sub al,48

	jmp .DONE

  .A_Z:

	; is AL in the range: 65 - 70 (A - B) ?

	cmp al,65

	jl .NOT_HEX

	cmp al,70

	jg .a_z

	sub al,55

	jmp .DONE

  .a_z:

	; is AL in the range: 97 - 102 (a - b) ?

	cmp al,97

	jl .NOT_HEX

	cmp al,102

	jg .NOT_HEX

	sub al,87

	jmp .DONE

  .NOT_HEX:

	; set EAX to 0xffff if input is not a valid hex digit

	mov eax,0xffff

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ByteToDigits

  ; convert 1 byte to 2 hex digits

  ; input in AL - output in AX

	; copy AL to AH

	mov ah,al

	; AH: get the upper 4 bits of the byte

	shr ah,4

	; nibble to hex digit

	add ah,48

	cmp ah,57

	jle .NEXT

	add ah,7

  .NEXT:

	; AL: get the lower 4 bits of the byte

	and al,0xf

	; nibble to hex digit

	add al,48

	cmp al,57

	jle .DONE

	add al,7

  .DONE:

	; output is in AX

	ret

endp

; -------------------------------------------------------------------------------------

proc LoadCalculations

  ; load the calculations from the bfDisplay string

	stdcall ResetCalculations

	mov [nErrorCode],byte 0

	lea esi,[bfDisplay]

  .NEXT:

	; get the next line

	lea edi,[CurrentLine]

	mov al,[esi]

	cmp al,0

	je .DONE

  .GET_LINE:

	; read the line into EDI (don't include CRLF)

	mov al,[esi]

	cmp al,13

	je .CRLF

	cmp al,10

	je .CRLF

	cmp al,0

	je .PROCESS

	mov [edi],al

	inc esi
	inc edi

	jmp .GET_LINE

  .CRLF:

	; advance ESI to the first non-CRLF (ready for the next line)

	inc esi

	mov al,[esi]

	cmp al,13

	je .CRLF

	cmp al,10

	je .CRLF

  .PROCESS:

	; null terminate EDI

	mov [edi],byte 0

	; process the line in EDI

	stdcall ProcessLine

	cmp al,0xff

	je .DONE

	inc [nCalculation]

	cmp [nCalculation],N_CALCS

	jge .DONE

	jmp .NEXT

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ProcessLine uses esi edi

  ; the current calculation (line) is in the string CurrentLine

  ; convert to instr and param codes, and store in the Calculations buffer

  ; structure of Calculations buffer:

  ; 1 calculation = 20 bytes

  ; byte 1 = operation code
  ; byte 2 = arg 1 code
  ; byte 3 = arg 2 code
  ; byte 4 = arg 3 code
  ; byte 5-20 = 16 byte immediate value

  ; total size of Calculations buffer = 800 bytes = 40 calculations

  ; set via N_CALCS and CALCS_BUFFER_SZ

	locals

	  instrcode db 0

	endl

	lea esi,[CurrentLine]

	lea edi,[Calculations]

	; adjust EDI to point to the correct location in the Calculations buffer

	xor edx,edx

	; multiply by 20 = shl(DL,4) + shl(DL,2)

	mov dl,[nCalculation]

	shl edx,4

	xor eax,eax

	mov al,[nCalculation]

	shl eax,2

	add edx,eax

	; nCalculation has been multiplied by 20, now add to EDI

	add edi,edx

	; skip any leading spaces in the line

	stdcall SkipSpaces

	; get the instruction

	stdcall GetInstrCode

	cmp al,0xff

	je .DONE

	; store the instr code in the Calculations buffer

	mov [edi],al

	inc edi

	; also save to instrcode

	mov [instrcode],al

  ; instr codes 1 to 31 - 2 arguments

  ; instr codes 32 to 63 - 1 argument + 1 immediate

  ; instr codes 64 to 95 - 1 argument

  ; instr code 96 to 127 - 1 immediate

	cmp [instrcode],95

	jg .96_127

	; get the first argument

	stdcall SkipSpaces

	cmp al,0xff

	je .DONE

	stdcall GetNextArgument

	cmp al,0xff

	je .DONE

	; store the arg code in the Calculations buffer

	mov [edi],al

	inc edi

  .1_31:

	cmp [instrcode],31

	jg .32_63

	; get the second argument

	stdcall SkipSpaces

	cmp al,0xff

	je .DONE

	stdcall GetNextArgument

	cmp al,0xff

	je .DONE

	; store the arg code in the Calculations buffer

	mov [edi],al

	inc edi

	jmp .EXTRA_CHARS

  .32_63:

	cmp [instrcode],63

	jg .64_95

	; EDI points to the location in the Calculations buffer
	; where the 128 bit immediate value will be stored

	inc edi
	inc edi

	; get the immediate value

	stdcall SkipSpaces

	cmp al,0xff

	je .DONE

	stdcall GetImmediate

	; is the Immediate argument in decimal or hex

	cmp [instrcode],38

	jl .HEX_IMMED

	cmp [instrcode],43

	jg .HEX_IMMED

  .DEC_IMMED:

	stdcall DecImmediateToHex

	jmp .NEXT

  .HEX_IMMED:

	stdcall ImmediateToHex

  .NEXT:

	cmp al,0xff

	je .DONE

	stdcall ImmediateToCalcsBuffer

	jmp .EXTRA_CHARS

  .64_95:

	cmp [instrcode],95

	jg .96_127

	jmp .EXTRA_CHARS

  .96_127:

	; EDI points to the location in the Calculations buffer
	; where the 128 bit immediate value will be stored

	inc edi
	inc edi
	inc edi

	; get the immediate value

	stdcall SkipSpaces

	cmp al,0xff

	je .DONE

	stdcall GetImmediate

	; immediate value is in decimal

	stdcall DecImmediateToHex

	cmp al,0xff

	je .DONE

	stdcall ImmediateToCalcsBuffer

  .EXTRA_CHARS:

	stdcall CheckExtraChars

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc CheckExtraChars

  ; the instr and args have been read

  ; are there still unread chars on the line ?

  ; spaces are OK

  ; ESI is set outside of this procedure

	mov al,0

  .CHECK:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	cmp dl,32

	je .NEXT

	mov al,0xff

	mov [nErrorCode],byte 1

	jmp .DONE

  .NEXT:

	inc esi

	jmp .CHECK

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc GetImmediate uses edi

  ; read the immediate hex value in ESI

  ; ESI is set outside of this procedure

	; read up to 32 hex digits

	lea edi,[bfImmediate]

	mov cx,0

	; read until either a space or a NULL is found

  .READ:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	cmp dl,32

	je .DONE

	mov [edi],dl

	inc esi

	inc edi

	inc cx

	; read up to 32 digits

	cmp cx,32

	jl .READ

  .DONE:

	; null terminate

	mov [edi],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc ImmediateToHex uses esi edi

  ; convert the string in bfImmediate to a 16 byte hex value

	lea edi,[Immediate]

	mov cx,0

	; zero the dest value

  .CLEAR:

	mov [edi],byte 0

	inc edi

	inc cx

	cmp cx,16

	jl .CLEAR

	; find the end of the bfImmediate string

	xor ecx,ecx

	lea esi,[bfImmediate]

  .EOS:

	; seek the null terminator, or count 32 digits

	mov dl,[esi]

	cmp dl,0

	je .NEXT

	inc esi

	inc cx

	cmp cx,32

	jl .EOS

  .NEXT:

	cmp cx,0

	je .DONE

	dec esi

	lea edi,[Immediate]

	; CX contains the digit count

  .CONVERT:

	; read the digits from right to left (in pairs)

	mov al,[esi]

	stdcall HexDigitToValue

	cmp eax,0xffff

	je .ERROR

	; put the first digit into EDI

	mov [edi],al

	dec cx

	cmp cx,0

	je .DONE

	dec esi

	mov al,[esi]

	stdcall HexDigitToValue

	cmp eax,0xffff

	je .ERROR

	shl al,4

	; OR the next digit into EDI

	or [edi],al

	dec cx

	cmp cx,0

	je .DONE

	dec esi

	inc edi

	jmp .CONVERT

	mov al,0

	jmp .DONE

  .ERROR:

	mov [nErrorCode],byte 2

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc DecImmediateToHex uses esi edi

  ; convert the string in bfImmediate to a hex value

  ; the value in the string is read as a decimal number

  ; it cannot be greater than 3200 (4 decimal digits)

	; count the number of digits in bfImmediate

	lea esi,[bfImmediate]

	mov cx,0

	xor eax,eax

  .COUNT:

	mov dl,[esi]

	cmp dl,0

	je .NEXT

	; check for non decimal digits

	cmp dl,48

	jl .ERROR

	cmp dl,57

	jg .ERROR

	; copy to EAX

	shl eax,8

	mov al,dl

	inc esi
	inc cx

	; check the count

	cmp cx,4

	jg .ERROR

	jmp .COUNT

  .NEXT:

	lea edi,[Immediate]

	mov cx,0

	; zero the dest value

  .CLEAR:

	mov [edi],byte 0

	inc edi

	inc cx

	cmp cx,16

	jl .CLEAR

	; decimal digits are in EAX - convert to a hex number

	stdcall DecToHex

	cmp eax,3200

	jg .ERROR

	; save the result to Immediate

	lea edi,[Immediate]

	mov [edi],eax

	; don't want to accidently return AL = 0xff

	xor eax,eax

	jmp .DONE

  .ERROR:

	mov al,0xff

	mov [nErrorCode],byte 6

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc DecToHex

  ; convert the decimal digit chars in EAX to a hex value

  ; result returned in EAX

	; swap the bytes

	bswap eax

	; find the most significant digit

	cmp al,0

	jne .NEXT

	shr eax,8

	cmp al,0

	jne .NEXT

	shr eax,8

	cmp al,0

	jne .NEXT

	shr eax,8

	; if AL is zero here, there were no digits in EAX

	cmp al,0

	je .DONE

  .NEXT:

	; the most significant digit is now in AL

	xor edx,edx

  .CONVERT:

	; if AL is zero, there are no more digits

	cmp al,0

	je .HEX

	; multiply the existing value in EDX by 10

	mov ebx,edx

	shl edx,3

	shl ebx,1

	add edx,ebx

	; put the next digit into EBX

	xor ebx,ebx

	mov bl,al

	sub bl,48

	add edx,ebx

	shr eax,8

	jmp .CONVERT

  .HEX:

	mov eax,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ImmediateToCalcsBuffer uses esi

  ; copy the immediate hex value to the Calculations buffer

  ; EDI is set outside of this procedure

	lea esi,[Immediate]

	mov cx,0

  .COPY:

	mov dl,[esi]

	mov [edi],dl

	inc esi
	inc edi

	inc cx

	cmp cx,16

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc GetNextArgument uses edi

  ; get the next argument from the string in ESI

  ; ESI set outside of this procedure

  ; code for argument returned in AL:

  ; Param_1 = 1
  ; Param_2 = 2
  ; Param_3 = 3
  ; Param_4 = 4
  ; Param_5 = 5
  ; Param_6 = 6
  ; Param_7 = 7
  ; Param_8 = 8
  ; Param_9 = 9
  ; Param_10 = 10

  ; Reg_1 = 11
  ; Reg_2 = 12
  ; Reg_3 = 13
  ; Reg_4 = 14

	lea edi,[CurrentArg]

	mov cx,0

  .GET_ARG:

	; read into EDI until a space or null is found

	mov al,[esi]

	cmp al,0

	je .GET_CODE

	cmp al,32

	je .GET_CODE

	mov [edi],al

	inc cx

	cmp cx,8

	jge .GET_CODE

	inc esi
	inc edi

	jmp .GET_ARG

  .GET_CODE:

	; null terminate EDI

	mov [edi],byte 0

	cmp cx,0

	jl .ERROR

	; test the string in EDI to get the argument code

	stdcall Test_Argument

	jmp .DONE

  .ERROR:

	; error code

	mov al,0xff

	mov [nErrorCode],byte 3

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Test_Argument uses esi edi

  ; test the argument against the symbols

	; symbol 1

	lea esi,[CurrentArg]

	lea edi,[Symbol_1]

	stdcall MatchSymbol

	mov dl,1

	cmp al,0

	je .SET_CODE

	; symbol 2

	lea esi,[CurrentArg]

	lea edi,[Symbol_2]

	stdcall MatchSymbol

	mov dl,2

	cmp al,0

	je .SET_CODE

	; symbol 3

	lea esi,[CurrentArg]

	lea edi,[Symbol_3]

	stdcall MatchSymbol

	mov dl,3

	cmp al,0

	je .SET_CODE

	; symbol 4

	lea esi,[CurrentArg]

	lea edi,[Symbol_4]

	stdcall MatchSymbol

	mov dl,4

	cmp al,0

	je .SET_CODE

	; symbol 5

	lea esi,[CurrentArg]

	lea edi,[Symbol_5]

	stdcall MatchSymbol

	mov dl,5

	cmp al,0

	je .SET_CODE

	; symbol 6

	lea esi,[CurrentArg]

	lea edi,[Symbol_6]

	stdcall MatchSymbol

	mov dl,6

	cmp al,0

	je .SET_CODE

	; symbol 7

	lea esi,[CurrentArg]

	lea edi,[Symbol_7]

	stdcall MatchSymbol

	mov dl,7

	cmp al,0

	je .SET_CODE

	; symbol 8

	lea esi,[CurrentArg]

	lea edi,[Symbol_8]

	stdcall MatchSymbol

	mov dl,8

	cmp al,0

	je .SET_CODE

	; symbol 9

	lea esi,[CurrentArg]

	lea edi,[Symbol_9]

	stdcall MatchSymbol

	mov dl,9

	cmp al,0

	je .SET_CODE

	; symbol 10

	lea esi,[CurrentArg]

	lea edi,[Symbol_10]

	stdcall MatchSymbol

	mov dl,10

	cmp al,0

	je .SET_CODE

	; symbol 11

	lea esi,[CurrentArg]

	lea edi,[Symbol_11]

	stdcall MatchSymbol

	mov dl,11

	cmp al,0

	je .SET_CODE

	; symbol 12

	lea esi,[CurrentArg]

	lea edi,[Symbol_12]

	stdcall MatchSymbol

	mov dl,12

	cmp al,0

	je .SET_CODE

	; symbol 13

	lea esi,[CurrentArg]

	lea edi,[Symbol_13]

	stdcall MatchSymbol

	mov dl,13

	cmp al,0

	je .SET_CODE

	; symbol 14

	lea esi,[CurrentArg]

	lea edi,[Symbol_14]

	stdcall MatchSymbol

	mov dl,14

	cmp al,0

	je .SET_CODE

	; match not found

	mov dl,0xff

	mov [nErrorCode],byte 4

  .SET_CODE:

	; return the code in AL

	mov al,dl

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc MatchSymbol

  ; test the name of the argument in ESI against the symbol name in EDI

  ; ESI and EDI are set outside of this procedure

  ; result returned in AL

  ; AL = 0 is a match

	mov al,0xff

	mov cx,0

  .TEST:

	mov dl,[esi]

	cmp [edi],dl

	jne .DONE

	cmp [edi],byte 0

	je .MATCH

	inc esi
	inc edi

	inc cx

	cmp cx,8

	jl .TEST

  .MATCH:

	mov al,0

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc GetInstrCode uses edi

  ; get the instr code from the string in ESI

  ; ESI set outside of this procedure

  ; instr code returned in AL

	lea edi,[CurrentInstr]

	mov cx,0

  .GET_INSTR:

	; read the chars into EDI, stop when a space or null is found

	mov al,[esi]

	cmp al,0

	je .GET_CODE

	mov [edi],al

	inc cx

	cmp al,32

	je .GET_CODE

	cmp cx,8

	jge .GET_CODE

	inc esi
	inc edi

	jmp .GET_INSTR

  .GET_CODE:

	cmp cx,3

	jl .ERROR

	; convert letters in EDI to upper case

	stdcall InstrToUpper

	; test the string in EDI to get an instr code

	stdcall Test_Instr

	cmp al,0xff

	jne .DONE

  .ERROR:

	mov [nErrorCode],byte 5

	mov al,0xff

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Test_Instr uses edi

  ; find the instr code for the instr in CurrentInstr

  ; return in AL

	; not found code is FF

	mov al,0xff

	lea edi,[CurrentInstr]

	;  =========================================================================

	;  2 ARGS:

	;  MOV = 1  ADD = 2  SUB = 3  MUL = 4  DIV = 5

	;  MOD = 6  AND = 7  OR = 8   XOR = 9  XCHG = 10

	;  =========================================================================

	; MOV = 1

	cmp [edi],byte 'M'
	jne .ADD
	cmp [edi+1],byte 'O'
	jne .ADD
	cmp [edi+2],byte 'V'
	jne .ADD
	cmp [edi+3],byte 32
	jne .ADD

	mov al,1
	jmp .DONE

  .ADD:

	; ADD = 2

	cmp [edi],byte 'A'
	jne .SUB
	cmp [edi+1],byte 'D'
	jne .SUB
	cmp [edi+2],byte 'D'
	jne .SUB
	cmp [edi+3],byte 32
	jne .SUB

	mov al,2
	jmp .DONE

  .SUB:

	; SUB = 3

	cmp [edi],byte 'S'
	jne .MUL
	cmp [edi+1],byte 'U'
	jne .MUL
	cmp [edi+2],byte 'B'
	jne .MUL
	cmp [edi+3],byte 32
	jne .MUL

	mov al,3
	jmp .DONE

  .MUL:

	; MUL = 4

	cmp [edi],byte 'M'
	jne .DIV
	cmp [edi+1],byte 'U'
	jne .DIV
	cmp [edi+2],byte 'L'
	jne .DIV
	cmp [edi+3],byte 32
	jne .DIV

	mov al,4
	jmp .DONE

  .DIV:

	; DIV = 5

	cmp [edi],byte 'D'
	jne .MOD
	cmp [edi+1],byte 'I'
	jne .MOD
	cmp [edi+2],byte 'V'
	jne .MOD
	cmp [edi+3],byte 32
	jne .MOD

	mov al,5
	jmp .DONE

  .MOD:

	; MOD = 6

	cmp [edi],byte 'M'
	jne .AND
	cmp [edi+1],byte 'O'
	jne .AND
	cmp [edi+2],byte 'D'
	jne .AND
	cmp [edi+3],byte 32
	jne .AND

	mov al,6
	jmp .DONE

  .AND:

	; AND = 7

	cmp [edi],byte 'A'
	jne .OR
	cmp [edi+1],byte 'N'
	jne .OR
	cmp [edi+2],byte 'D'
	jne .OR
	cmp [edi+3],byte 32
	jne .OR

	mov al,7
	jmp .DONE

  .OR:

	; OR = 8

	cmp [edi],byte 'O'
	jne .XOR
	cmp [edi+1],byte 'R'
	jne .XOR
	cmp [edi+2],byte 32
	jne .XOR

	mov al,8
	jmp .DONE

  .XOR:

	; XOR = 9

	cmp [edi],byte 'X'
	jne .XCHG
	cmp [edi+1],byte 'O'
	jne .XCHG
	cmp [edi+2],byte 'R'
	jne .XCHG
	cmp [edi+3],byte 32
	jne .XCHG

	mov al,9
	jmp .DONE

  .XCHG:

	; XCHG = 10

	cmp [edi],byte 'X'
	jne .MOVI
	cmp [edi+1],byte 'C'
	jne .MOVI
	cmp [edi+2],byte 'H'
	jne .MOVI
	cmp [edi+3],byte 'G'
	jne .MOVI
	cmp [edi+4],byte 32
	jne .MOVI

	mov al,10
	jmp .DONE

	;  =========================================================================

	;  1 ARG + 1 IMMED:

	;  MOVI = 32  ADDI = 33  SUBI = 34  ANDI = 35  ORI = 36  XORI = 37

	;  SHL = 38   SHR = 39   ROTL = 40  ROTR = 41

	;  TRUNC = 42  BSET = 43

	;  MULI = 44  DIVI = 45  MODI = 46

	;  =========================================================================

  .MOVI:

	; MOVI = 32

	cmp [edi],byte 'M'
	jne .ADDI
	cmp [edi+1],byte 'O'
	jne .ADDI
	cmp [edi+2],byte 'V'
	jne .ADDI
	cmp [edi+3],byte 'I'
	jne .ADDI
	cmp [edi+4],byte 32
	jne .ADDI

	mov al,32
	jmp .DONE

  .ADDI:

	; ADDI = 33

	cmp [edi],byte 'A'
	jne .SUBI
	cmp [edi+1],byte 'D'
	jne .SUBI
	cmp [edi+2],byte 'D'
	jne .SUBI
	cmp [edi+3],byte 'I'
	jne .SUBI
	cmp [edi+4],byte 32
	jne .SUBI

	mov al,33
	jmp .DONE

  .SUBI:

	; SUBI = 34

	cmp [edi],byte 'S'
	jne .ANDI
	cmp [edi+1],byte 'U'
	jne .ANDI
	cmp [edi+2],byte 'B'
	jne .ANDI
	cmp [edi+3],byte 'I'
	jne .ANDI
	cmp [edi+4],byte 32
	jne .ANDI

	mov al,34
	jmp .DONE

  .ANDI:

	; ANDI = 35

	cmp [edi],byte 'A'
	jne .ORI
	cmp [edi+1],byte 'N'
	jne .ORI
	cmp [edi+2],byte 'D'
	jne .ORI
	cmp [edi+3],byte 'I'
	jne .ORI
	cmp [edi+4],byte 32
	jne .ORI

	mov al,35
	jmp .DONE

  .ORI:

	; ORI = 36

	cmp [edi],byte 'O'
	jne .XORI
	cmp [edi+1],byte 'R'
	jne .XORI
	cmp [edi+2],byte 'I'
	jne .XORI
	cmp [edi+3],byte 32
	jne .XORI

	mov al,36
	jmp .DONE

  .XORI:

	; XORI = 37

	cmp [edi],byte 'X'
	jne .SHL
	cmp [edi+1],byte 'O'
	jne .SHL
	cmp [edi+2],byte 'R'
	jne .SHL
	cmp [edi+3],byte 'I'
	jne .SHL
	cmp [edi+4],byte 32
	jne .SHL

	mov al,37
	jmp .DONE

  .SHL:

	; SHL = 38

	cmp [edi],byte 'S'
	jne .SHR
	cmp [edi+1],byte 'H'
	jne .SHR
	cmp [edi+2],byte 'L'
	jne .SHR
	cmp [edi+3],byte 32
	jne .SHR

	mov al,38
	jmp .DONE

  .SHR:

	; SHR = 39

	cmp [edi],byte 'S'
	jne .ROTL
	cmp [edi+1],byte 'H'
	jne .ROTL
	cmp [edi+2],byte 'R'
	jne .ROTL
	cmp [edi+3],byte 32
	jne .ROTL

	mov al,39
	jmp .DONE

  .ROTL:

	; ROTL = 40

	cmp [edi],byte 'R'
	jne .ROTR
	cmp [edi+1],byte 'O'
	jne .ROTR
	cmp [edi+2],byte 'T'
	jne .ROTR
	cmp [edi+3],byte 'L'
	jne .ROTR
	cmp [edi+4],byte 32
	jne .ROTR

	mov al,40
	jmp .DONE

  .ROTR:

	; ROTR = 41

	cmp [edi],byte 'R'
	jne .TRUNC
	cmp [edi+1],byte 'O'
	jne .TRUNC
	cmp [edi+2],byte 'T'
	jne .TRUNC
	cmp [edi+3],byte 'R'
	jne .TRUNC
	cmp [edi+4],byte 32
	jne .TRUNC

	mov al,41
	jmp .DONE

  .TRUNC:

	; TRUNC = 42

	cmp [edi],byte 'T'
	jne .BSET
	cmp [edi+1],byte 'R'
	jne .BSET
	cmp [edi+2],byte 'U'
	jne .BSET
	cmp [edi+3],byte 'N'
	jne .BSET
	cmp [edi+4],byte 'C'
	jne .BSET
	cmp [edi+5],byte 32
	jne .BSET

	mov al,42
	jmp .DONE

  .BSET:

	; BSET = 43

	cmp [edi],byte 'B'
	jne .MULI
	cmp [edi+1],byte 'S'
	jne .MULI
	cmp [edi+2],byte 'E'
	jne .MULI
	cmp [edi+3],byte 'T'
	jne .MULI
	cmp [edi+4],byte 32
	jne .MULI

	mov al,43
	jmp .DONE

  .MULI:

	; MULI = 44

	cmp [edi],byte 'M'
	jne .DIVI
	cmp [edi+1],byte 'U'
	jne .DIVI
	cmp [edi+2],byte 'L'
	jne .DIVI
	cmp [edi+3],byte 'I'
	jne .DIVI
	cmp [edi+4],byte 32
	jne .DIVI

	mov al,44
	jmp .DONE

  .DIVI:

	; DIVI = 45

	cmp [edi],byte 'D'
	jne .MODI
	cmp [edi+1],byte 'I'
	jne .MODI
	cmp [edi+2],byte 'V'
	jne .MODI
	cmp [edi+3],byte 'I'
	jne .MODI
	cmp [edi+4],byte 32
	jne .MODI

	mov al,45
	jmp .DONE

  .MODI:

	; MODI = 46

	cmp [edi],byte 'M'
	jne .NOT
	cmp [edi+1],byte 'O'
	jne .NOT
	cmp [edi+2],byte 'D'
	jne .NOT
	cmp [edi+3],byte 'I'
	jne .NOT
	cmp [edi+4],byte 32
	jne .NOT

	mov al,46
	jmp .DONE

	;  =========================================================================

	;  1 ARG:

	;  NOT = 64  NEG = 65  CLR = 67

	;  =========================================================================

  .NOT:

	; NOT = 64

	cmp [edi],byte 'N'
	jne .NEG
	cmp [edi+1],byte 'O'
	jne .NEG
	cmp [edi+2],byte 'T'
	jne .NEG
	cmp [edi+3],byte 32
	jne .NEG

	mov al,64
	jmp .DONE

  .NEG:

	; NEG = 65

	cmp [edi],byte 'N'
	jne .CLR
	cmp [edi+1],byte 'E'
	jne .CLR
	cmp [edi+2],byte 'G'
	jne .CLR
	cmp [edi+3],byte 32
	jne .CLR

	mov al,65
	jmp .DONE

  .CLR:

	; CLR = 66

	cmp [edi],byte 'C'
	jne .NBYTES
	cmp [edi+1],byte 'L'
	jne .NBYTES
	cmp [edi+2],byte 'R'
	jne .NBYTES
	cmp [edi+3],byte 32
	jne .NBYTES

	mov al,66

	;  =========================================================================

	;  1 IMMED:

	;  NBYTES = 96  REPEAT = 97

	;  =========================================================================

  .NBYTES:

	; NBYTES = 96

	cmp [edi],byte 'N'
	jne .REPEAT
	cmp [edi+1],byte 'B'
	jne .REPEAT
	cmp [edi+2],byte 'Y'
	jne .REPEAT
	cmp [edi+3],byte 'T'
	jne .REPEAT
	cmp [edi+4],byte 'E'
	jne .REPEAT
	cmp [edi+5],byte 'S'
	jne .REPEAT
	cmp [edi+6],byte 32
	jne .REPEAT

	mov al,96

  .REPEAT:

	; REPEAT = 97

	cmp [edi],byte 'R'
	jne .DONE
	cmp [edi+1],byte 'E'
	jne .DONE
	cmp [edi+2],byte 'P'
	jne .DONE
	cmp [edi+3],byte 'E'
	jne .DONE
	cmp [edi+4],byte 'A'
	jne .DONE
	cmp [edi+5],byte 'T'
	jne .DONE
	cmp [edi+6],byte 32
	jne .DONE

	mov al,97

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc InstrToUpper uses edi

  ; convert all letters in the instr to upper case

	lea edi,[CurrentInstr]

	mov cx,0

  .CONVERT:

	mov al,[edi]

	cmp al,97

	jl .NEXT

	cmp al,122

	jg .NEXT

	sub al,32

	mov [edi],al

  .NEXT:

	inc edi
	inc cx

	cmp cx,8

	jl .CONVERT

	ret

endp

; -------------------------------------------------------------------------------------

proc SkipSpaces

  ; ESI is set outside of this procedure

  ; advance ESI to the first non space char

	mov al,0

  .NEXT:

	mov dl,[esi]

	cmp dl,32

	jne .TEST

	inc esi

	jmp .NEXT

  .TEST:

	cmp dl,byte 0

	jne .DONE

	; a non space char was not found

	mov al,0xff

	mov [nErrorCode],byte 3

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ValidateParamsBuffer uses esi

  ; make sure that bfDisplay - when used as the input buffer
  ; for the parameters - only contains valid chars

  ; 0-9 = ascii 48-57
  ; A-Z = ascii 65-90
  ; a-z = ascii 97-122
  ; space = ascii 32
  ; underscore = ascii 95
  ; crlf = 13,10
  ; {} = 123,125

  ; return the result in AL

	mov al,0

	lea esi,[bfDisplay]

  .SCAN:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	cmp dl,123

	je .NEXT

	cmp dl,125

	je .NEXT

	stdcall ValidateChar

	cmp al,0xff

	je .DONE

  .NEXT:

	inc esi

	jmp .SCAN

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ValidateCalcsBuffer uses esi

  ; make sure that bfDisplay - when used as the input buffer
  ; for the calculations - only contains valid chars

  ; 0-9 = ascii 48-57
  ; A-Z = ascii 65-90
  ; a-z = ascii 97-122
  ; space = ascii 32
  ; underscore = ascii 95
  ; crlf = 13,10

  ; return the result in AL

	mov al,0

	lea esi,[bfDisplay]

  .SCAN:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	stdcall ValidateChar

	cmp al,0xff

	je .DONE

	inc esi

	jmp .SCAN

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ValidateChar

  ; test the char passed in DL

  ; set AL to 0xff if invalid

  ; 0-9 = ascii 48-57
  ; A-Z = ascii 65-90
  ; a-z = ascii 97-122
  ; space = ascii 32
  ; underscore = ascii 95

	cmp dl,32

	je .DONE

	cmp dl,10

	je .DONE

	cmp dl,13

	je .DONE

	cmp dl,95

	je .DONE

	cmp dl,48

	jl .INVALID

	cmp dl,122

	jg .INVALID

	cmp dl,58

	jl .DONE

	cmp dl,65

	jl .INVALID

	cmp dl,91

	jl .DONE

	cmp dl,96

	jg .DONE

  .INVALID:

	mov al,0xff

	mov [nErrorCode],byte 7

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ResetCalculations uses edi ecx

  ; clear the Calculations buffer

	mov [nCalculation],byte 0

	lea edi,[Calculations]

	mov cx,0

  .CLEAR:

	mov [edi],byte 0

	inc edi
	inc cx

	cmp cx,CALCS_BUFFER_SZ

	jl .CLEAR

	ret

endp

; -------------------------------------------------------------------------------------

proc PrintErrorMessage uses esi edi

  ; print the error message to bfDisplay

	lea edi,[bfDisplay]

	mov dl,[nErrorCode]

  .ERR_1:

	cmp dl,1

	jne .ERR_2

	lea esi,[szError_1]

	jmp .PRINT

  .ERR_2:

	cmp dl,2

	jne .ERR_3

	lea esi,[szError_2]

	jmp .PRINT

  .ERR_3:

	cmp dl,3

	jne .ERR_4

	lea esi,[szError_3]

	jmp .PRINT

  .ERR_4:

	cmp dl,4

	jne .ERR_5

	lea esi,[szError_4]

	jmp .PRINT

  .ERR_5:

	cmp dl,5

	jne .ERR_6

	lea esi,[szError_5]

	jmp .PRINT

  .ERR_6:

	cmp dl,6

	jne .ERR_7

	lea esi,[szError_6]

	jmp .PRINT

  .ERR_7:

	cmp dl,7

	jne .ERR_8

	lea esi,[szError_7]

	jmp .PRINT

  .ERR_8:

	cmp dl,8

	jne .ERR_0

	lea esi,[szError_8]

	jmp .PRINT

  .ERR_0:

	lea esi,[szError_0]

  .PRINT:

	mov dl,[esi]

	cmp dl,0

	je .MORE

	mov [edi],dl

	inc esi
	inc edi

	jmp .PRINT

  .MORE:

	mov dl,[nErrorCode]

	cmp dl,1

	jl .DONE

	cmp dl,6

	jg .DONE

	; CRLF x 2

	PRINT_CRLF
	PRINT_CRLF

	lea esi,[CurrentLine]

  .PRINTL:

	mov dl,[esi]

	cmp dl,0

	je .DONE

	mov [edi],dl

	inc esi
	inc edi

	jmp .PRINTL

  .DONE:

	mov [edi],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc PrintExeError uses esi edi

  ; print the execution error message to the bfDisplay string

	locals

	  count db 0

	endl

	lea edi,[bfDisplay]

	; execution error codes:

	; 0 = unknown error

	; 1 = unknown instruction or argument code

	; 2 = divide by zero error

	cmp [nExeErrorCode],byte 1

	je .EXE_ERR_1

	cmp [nExeErrorCode],byte 2

	je .EXE_ERR_2

  .EXE_ERR_0:

	lea esi,[szExeError_0]

  .SZ_0:

	mov al,[esi]

	cmp al,0

	je .BF_CALCS

	mov [edi],al

	inc esi
	inc edi

	jmp .SZ_0

  .EXE_ERR_1:

	lea esi,[szExeError_1]

  .SZ_1:

	mov al,[esi]

	cmp al,0

	je .BF_CALCS

	mov [edi],al

	inc esi
	inc edi

	jmp .SZ_1

  .EXE_ERR_2:

	lea esi,[szExeError_2]

  .SZ_2:

	mov al,[esi]

	cmp al,0

	je .DONE

	mov [edi],al

	inc esi
	inc edi

	jmp .SZ_2

  .BF_CALCS:

	; print the calculations buffer

	lea esi,[Calculations]

	PRINT_CRLF
	PRINT_CRLF

	mov cx,CALCS_BUFFER_SZ

  .PRINT:

	; byte to digits

	mov al,[esi]

	stdcall ByteToDigits

	; add the 2 digits to the string

	mov [edi],ah
	inc edi

	mov [edi],al
	inc edi

	inc [count]

	cmp [count],20

	je .CRLF

	jmp .NEXT

  .CRLF:

	mov [count],byte 0

	PRINT_CRLF

  .NEXT:

	inc esi

	dec cx

	cmp cx,0

	jg .PRINT

  .DONE:

	; NULL terminate

	mov [edi],byte 0

	; reset the error flag

	mov [nExeErrorCode],byte 0

	ret

endp

; -------------------------------------------------------------------------------------

proc ExecuteCalculations uses esi edi

  ; execute the instructions in the Calculations buffer

	lea esi,[Calculations]

	mov cx,0

  .INSTR:

	mov [nExeErrorCode],byte 0

	; get the next instruction

	xor eax,eax

	; put instr code, arg 1 index, arg 2 index into EAX

	mov eax,[esi]

	; instr code is in AL

	; instr code = 0, no more instructions

	cmp al,0

	je .DONE

	cmp al,32

	jge .32_63

  .1_31:

	cmp al,1
	je .1
	cmp al,2
	je .2
	cmp al,3
	je .3
	cmp al,4
	je .4
	cmp al,5
	je .5
	cmp al,6
	je .6
	cmp al,7
	je .7
	cmp al,8
	je .8
	cmp al,9
	je .9
	cmp al,10
	je .10

	jmp .UI_ERROR

  .1:

	; MOV

	stdcall Mov_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .2:

	; ADD

	stdcall Add_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .3:

	; SUB

	stdcall Sub_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .4:

	; MUL

	stdcall Mul_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .5:

	; DIV

	stdcall Div_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .6:

	; MOD

	stdcall Mod_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .7:

	; AND

	stdcall And_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .8:

	; OR

	stdcall Or_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .9:

	; XOR

	stdcall Xor_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .10:

	; XCHG

	stdcall Xchg_Dest_Src

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .32_63:

	cmp al,64

	jge .64_95

	; get the immediate value

	lea edi,[Immediate]

	mov edx,[esi+4]
	mov [edi],edx
	mov edx,[esi+8]
	mov [edi+4],edx
	mov edx,[esi+12]
	mov [edi+8],edx
	mov edx,[esi+16]
	mov [edi+12],edx

	cmp al,32
	je .32
	cmp al,33
	je .33
	cmp al,34
	je .34
	cmp al,35
	je .35
	cmp al,36
	je .36
	cmp al,37
	je .37
	cmp al,38
	je .38
	cmp al,39
	je .39
	cmp al,40
	je .40
	cmp al,41
	je .41
	cmp al,42
	je .42
	cmp al,43
	je .43
	cmp al,44
	je .44
	cmp al,45
	je .45
	cmp al,46
	je .46

	jmp .UI_ERROR

  .32:

	; MOVI

	stdcall Mov_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .33:

	; ADDI

	stdcall Add_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .34:

	; SUBI

	stdcall Sub_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .35:

	; ANDI

	stdcall And_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .36:

	; ORI

	stdcall Or_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .37:

	; XORI

	stdcall Xor_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .38:

	; SHL

	stdcall Shl_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .39:

	; SHR

	stdcall Shr_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .40:

	; ROTL

	stdcall Rotl_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .41:

	; ROTR

	stdcall Rotr_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .42:

	; TRUNC

	stdcall Trunc_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .43:

	; BSET

	stdcall Bset_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .44:

	; MULI

	stdcall Mul_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .45:

	; DIVI

	stdcall Div_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .46:

	; MODI

	stdcall Mod_Dest_Immed

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .64_95:

	cmp al,96

	jge .96_127

	cmp al,64
	je .64
	cmp al,65
	je .65
	cmp al,66
	je .66

	jmp .UI_ERROR

  .64:

	; NOT

	stdcall Not_Dest

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .65:

	; NEG

	stdcall Neg_Dest

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .66:

	; CLR

	stdcall Clr_Dest

	cmp dl,0xff

	je .UI_ERROR

	jmp .NEXT

  .96_127:

	; get the immediate value

	mov edx,[esi+4]

	cmp al,96
	je .96
	cmp al,97
	je .97

	jmp .UI_ERROR

  .96:

	; NBYTES

	mov [nBytes],dx

	jmp .NEXT

  .97:

	; REPEAT

	; immediate value is in EDX

	; put iteration number into EAX

	mov eax,[esi+8]

	cmp eax,edx

	jae .END_REPEAT

	inc eax

	mov [esi+8],eax

	lea esi,[Calculations]

	mov cx,0

	jmp .INSTR

  .END_REPEAT:

	mov [esi+8],dword 0

  .NEXT:

	cmp [nExeErrorCode],byte 0

	jne .EXE_ERROR

	add esi,20

	inc cx

	cmp cx,N_CALCS

	jl .INSTR

	jmp .DONE

  .UI_ERROR:

	; error - unknown instruction or argument code

	mov [nExeErrorCode],byte 1

  .EXE_ERROR:

	mov eax,0xffffffff

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mov_Dest_Src uses esi edi ecx

  ; copy the src value to the dest

  ; MOV dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; copy the src value to dest

	mov cx,0

  .COPY:

	mov dl,[esi]

	mov [edi],dl

	inc esi
	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Add_Dest_Src uses esi edi ecx

  ; ADD the src value to the dest

  ; ADD dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; add the src value to dest

	mov cx,0

	; save CF from iteration to iteration

	; init CF = 0

	clc

	; push flags register to the stack

	pushf

  .ADD:

	mov dl,[esi]

	; get the flags register from the stack

	popf

	adc [edi],dl

	; save the flags register to the stack, for the next iteration

	pushf

	inc esi
	inc edi
	inc cx

	cmp cx,HEX_LEN

	jl .ADD

	; clean up the stack

	popf

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Sub_Dest_Src uses esi edi ecx

  ; SUB the src value from the dest

  ; SUB dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; sub the src value from dest

	mov cx,0

	; save CF from iteration to iteration

	; init CF = 0

	clc

	; push flags register to the stack

	pushf

  .SUB:

	mov dl,[esi]

	; get the flags register from the stack

	popf

	sbb [edi],dl

	; save the flags register to the stack, for the next iteration

	pushf

	inc esi
	inc edi
	inc cx

	cmp cx,HEX_LEN

	jl .SUB

	; clean up the stack

	popf

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mul_Dest_Src uses esi edi ecx

  ; Multiply the dest value by the src value

  ; MUL dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; also need an intermediate value - the temp buffer

	; clear the intermediate value

	stdcall ClearTempBuffer

	xor ecx,ecx

	xor edx,edx

  .MULTIPLY:

	; get a byte from ESI and multiply the hex value in EDI

	; add the result to the temp buffer

	mov al,[esi]

	mov ebx,edi

	; the byte is passed in AL, the offset in DL, EDI in EBX

	stdcall ByteMultiply

	inc esi

	inc dx

	inc cx

	cmp cx,HEX_LEN

	jl .MULTIPLY

	; copy the result from the temp buffer back to EDI

	stdcall CopyFromTempBuffer

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ByteMultiply uses ecx edi

  ; multiply EDI by a byte value and add to TempBf

  ; EDI is passed in EBX

  ; byte is passed in AL

  ; offset is passed in DX

	locals

	  mbyte db 0

	endl

	; store the byte

	mov [mbyte],al

	; get EDI

	mov edi,ebx

	mov cx,0

	xor ebx,ebx

	; put the offset into BX (passed to Add_Word)

	mov bx,dx

  .B_MULT:

	; mul byte [EDI] = [EDI] * AL = |AH|AL| = AX

	mul byte [edi]

	; add AX to the temp buffer

	stdcall Add_Word

	; retore AL for the next iteration

	mov al,[mbyte]

	inc edi

	inc bx

	cmp bx,HEX_LEN

	jge .DONE

	inc cx

	cmp cx,HEX_LEN

	jl .B_MULT

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Add_Word uses edi ecx

  ; add the word in AX to the temp buffer

  ; at the offset given by BX

	cmp bx,HEX_LEN

	jge .DONE

	lea edi,[TempBf]

	xor ecx,ecx

	mov cx,bx

	cmp bx,HEX_LEN-1

	jl .ADD_WD

	add [edi+ecx],al

	jmp .DONE

  .ADD_WD:

	add [edi+ecx],al

	inc cx

	adc [edi+ecx],ah

	jnc .DONE

	inc cx

  .ADD_C:

	cmp cx,HEX_LEN

	jge .DONE

	add byte [edi+ecx],1

	jnc .DONE

	inc cx

	jmp .ADD_C

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Div_Dest_Src uses esi edi ecx

  ; Divide the dest value by the src

  ; DIV dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; reset the quotient and divisor

	stdcall Zero_Quot_Div

	; copy the source (ESI) to the divisor

	stdcall CopyToDivisor

	; do the division

	stdcall Divide

	; copy the quotient to the destination

	stdcall QuotientToDest

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mod_Dest_Src uses esi edi ecx

  ; compute dest mod src

  ; MOD dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; reset the quotient and divisor

	stdcall Zero_Quot_Div

	; copy the source (ESI) to the divisor

	stdcall CopyToDivisor

	; do the division

	stdcall Divide

	; remainder is in EDI (the destination)

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc And_Dest_Src uses esi edi ecx

  ; AND the src value into the dest

  ; AND dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; AND the src value into dest

	mov cx,0

  .AND:

	mov dl,[esi]

	and [edi],dl

	inc esi
	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .AND

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Or_Dest_Src uses esi edi ecx

  ; OR the src value into the dest

  ; OR dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; OR the src value into dest

	mov cx,0

  .OR:

	mov dl,[esi]

	or [edi],dl

	inc esi
	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .OR

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Xor_Dest_Src uses esi edi ecx

  ; XOR the src value into the dest

  ; XOR dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; XOR the src value into dest

	mov cx,0

  .XOR:

	mov dl,[esi]

	xor [edi],dl

	inc esi
	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .XOR

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Xchg_Dest_Src uses esi edi ecx

  ; exchange the src value and the dest value

  ; XCHG dest src

	shr eax,8

	; dest index is in AL and src index is in AH

	; get ESI for the src arg

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	mov esi,edi

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; copy ESI to the TempBf

	stdcall CopyToTempBuffer

	; copy EDI to ESI

	xor ecx,ecx

  .COPY:

	mov dl,[edi+ecx]

	mov [esi+ecx],dl

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	; copy from TempBf to EDI

	stdcall CopyFromTempBuffer

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mov_Dest_Immed uses esi edi ecx

  ; move the immediate value into the destination:

  ; MOVI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the destination

	stdcall ClearParam

	; get EDI again

	mov dl,ah

	stdcall GetDestPtr

	; copy in the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	mov [edi],edx

	mov edx,[esi+4]
	mov [edi+4],edx

	mov edx,[esi+8]
	mov [edi+8],edx

	mov edx,[esi+12]
	mov [edi+12],edx

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Add_Dest_Immed uses esi edi ecx

  ; add the immediate value to the destination:

  ; ADDI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; add in the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	add [edi],edx

	mov edx,[esi+4]
	adc [edi+4],edx

	mov edx,[esi+8]
	adc [edi+8],edx

	mov edx,[esi+12]
	adc [edi+12],edx

	jnc .DL_0

	add edi,16

	mov cx,16

  .ADD_C:

	add [edi],byte 1

	jnc .DL_0

	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .ADD_C

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Sub_Dest_Immed uses esi edi ecx

  ; subtract the immediate value from the destination:

  ; SUBI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; subtract the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	sub [edi],edx

	mov edx,[esi+4]
	sbb [edi+4],edx

	mov edx,[esi+8]
	sbb [edi+8],edx

	mov edx,[esi+12]
	sbb [edi+12],edx

	jnc .DL_0

	add edi,16

	mov cx,16

  .SUB_B:

	sub [edi],byte 1

	jnc .DL_0

	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .SUB_B

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc And_Dest_Immed uses esi edi ecx

  ; AND the immediate value into the destination:

  ; ANDI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; AND in the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	and [edi],edx

	mov edx,[esi+4]
	and [edi+4],edx

	mov edx,[esi+8]
	and [edi+8],edx

	mov edx,[esi+12]
	and [edi+12],edx

	; clear the rest of EDI

	xor ecx,ecx

	mov cx,16

  .CLEAR:

	mov [edi+ecx],byte 0

	inc cx

	cmp cx,HEX_LEN

	jl .CLEAR

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Or_Dest_Immed uses esi edi ecx

  ; OR the immediate value into the destination:

  ; ORI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; OR in the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	or [edi],edx

	mov edx,[esi+4]
	or [edi+4],edx

	mov edx,[esi+8]
	or [edi+8],edx

	mov edx,[esi+12]
	or [edi+12],edx

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Xor_Dest_Immed uses esi edi ecx

  ; XOR the immediate value into the destination:

  ; XORI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; XOR in the 16 byte immediate value

	lea esi,[Immediate]

	mov edx,[esi]
	xor [edi],edx

	mov edx,[esi+4]
	xor [edi+4],edx

	mov edx,[esi+8]
	xor [edi+8],edx

	mov edx,[esi+12]
	xor [edi+12],edx

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Shl_Dest_Immed uses esi edi ecx

  ; shift left dest by the number of bits given in immed

  ; SHL dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; check that nBytes is in range

	stdcall Check_nBytes

	; the number of bytes to apply the SHL to is now given by nBytes

	; get the number of bits to shift by

	lea esi,[Immediate]

	mov edx,[esi]

	; if the shift is greater or equal to nBytes, the result is zero

	xor eax,eax

	mov ax,[nBytes]

	shl ax,3

	cmp dx,ax

	jge .DL_0

	; copy EDI to the shift buffer

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	; do the shift

	stdcall ShiftLeft

  .DL_0:

	stdcall CopyFromShiftBuffer

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ShiftLeft uses esi edi ecx

  ; for the SHL operation

  ; SHL the shift buffer by shl_bytes + shl_bits

  ; size of shift in bits passed in EDX

  ; ShiftBf is used as the shift buffer

	locals

	  ncopy dw 0

	endl

	lea esi,[ShiftBf]

	lea edi,[ShiftBf]

	mov ebx,edx

	shr ebx,3

	; EBX now contains shl_bytes

	cmp bx,0

	je .BITS

	; ncopy = number of bytes to copy up = nBytes - shl_bytes

	mov ax,[nBytes]

	mov [ncopy],ax

	sub [ncopy],bx

  .BYTES:

	sub esi,ebx

	xor ecx,ecx

	mov cx,[nBytes]

	dec cx

  .COPY:

	; copy up the bytes

	mov bl,[esi+ecx]

	mov [edi+ecx],bl

	dec cx

	dec word [ncopy]

	cmp [ncopy],word 0

	jg .COPY

	cmp cx,0

	jl .BITS

  .ZERO:

	; zero the rest of the buffer

	mov [edi+ecx],byte 0

	dec cx

	cmp cx,0

	jge .ZERO

  .BITS:

	; calculate the size of the remaining bit shift (less than 8 bits)

	; calc EDX mod 8

	and edx,7

	cmp edx,0

	; if bits = 0, nothing more to do

	je .DONE

	stdcall SHL_Bits

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc SHL_Bits uses edi ecx eax

  ; shift size in bits is passed in DL

	lea edi,[ShiftBf]

	mov cl,dl

	; CL contains the shift size, so use AX as the counter

	xor eax,eax

	mov ax,[nBytes]

	dec ax

	add edi,eax

  .SHL:

	mov dh,[edi-1]

	mov bl,[edi]

	shld bx,dx,cl

	mov [edi],bl

	dec edi

	dec ax

	cmp ax,0

	jg .SHL

  .FIRST:

	shl byte [edi],cl

	ret

endp

; -------------------------------------------------------------------------------------

proc Shr_Dest_Immed uses esi edi ecx

  ; shift right dest by the number of bits given in immed

  ; SHR dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; check that nBytes is in range

	stdcall Check_nBytes

	; the number of bytes to apply the SHR to is now given by nBytes

	; get the number of bits to shift by

	lea esi,[Immediate]

	mov edx,[esi]

	; if the shift is greater or equal to nBytes, the result is zero

	xor eax,eax

	mov ax,[nBytes]

	shl ax,3

	cmp dx,ax

	jge .DL_0

	; copy EDI to the shift buffer

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	; do the shift

	stdcall ShiftRight

  .DL_0:

	stdcall CopyFromShiftBuffer

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ShiftRight uses esi edi ecx

  ; for the SHR operation

  ; SHR the shift buffer by shr_bytes + shr_bits

  ; size of shift in bits passed in EDX

  ; ShiftBf is used as the shift buffer

	locals

	  ncopy dw 0

	endl

	lea esi,[ShiftBf]

	lea edi,[ShiftBf]

	mov ebx,edx

	shr ebx,3

	; EBX now contains shr_bytes

	cmp bx,0

	je .BITS

	; ncopy = number of bytes to copy down = nBytes - shr_bytes

	mov ax,[nBytes]

	mov [ncopy],ax

	sub [ncopy],bx

  .BYTES:

	xor ecx,ecx

	add esi,ebx

  .COPY:

	; copy down the bytes

	mov bl,[esi+ecx]

	mov [edi+ecx],bl

	inc cx

	dec word [ncopy]

	cmp [ncopy],word 0

	jg .COPY

	cmp cx,[nBytes]

	jge .BITS

  .ZERO:

	; zero the rest of the buffer

	mov [edi+ecx],byte 0

	inc cx

	cmp cx,[nBytes]

	jl .ZERO

  .BITS:

	; calculate the size of the remaining bit shift (less than 8 bits)

	; calc EDX mod 8

	and edx,7

	cmp edx,0

	; if bits = 0, nothing more to do

	je .DONE

	stdcall SHR_Bits

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc SHR_Bits uses edi ecx eax

  ; shift size in bits is passed in DL

	lea edi,[ShiftBf]

	mov cl,dl

	; CL contains the shift size, so use AX as the counter

	xor eax,eax

  .SHR:

	mov dl,[edi+1]

	mov bh,[edi]

	shrd bx,dx,cl

	mov [edi],bh

	inc edi

	inc ax

	inc ax

	cmp ax,[nBytes]

	jge .LAST

	dec ax

	jmp .SHR

  .LAST:

	shr byte [edi],cl

	ret

endp

; -------------------------------------------------------------------------------------

proc Rotl_Dest_Immed uses esi edi ecx

  ; rot left dest by the number of bits given in immed

  ; ROTL dest immed

	locals

	  shift dw 0

	endl

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; check that nBytes is in range

	stdcall Check_nBytes

	; the number of bytes to apply the ROTL to is now given by nBytes

	; get the number of bits to shift by

	lea esi,[Immediate]

	mov edx,[esi]

	; if the number of bits to shift by is equal to (8 x nBytes) then no rot needed

	xor eax,eax

	mov ax,[nBytes]

	shl eax,3

	cmp eax,edx

	je .NO_ROT

	; if the number of bits to shift by is greater than (8 x nBytes)

	; calc EDX = EDX mod EAX

  .MOD:

	cmp edx,eax

	jl .B_COPY

	sub edx,eax

	cmp edx,eax

	; if (EDX mod EAX) = 0 then no rot needed

	je .NO_ROT

	jmp .MOD

  .B_COPY:

	mov [shift],dx

	; copy EDI to the shift buffer

	; ROTL(x) = SHL(x) or SHR(nBytes-x)

	; do the left shift

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	stdcall ShiftLeft

	lea esi,[ShiftBf]

	; copy the result to the temp buffer

	stdcall CopyToTempBuffer

	; ROTL(x) = SHL(x) or SHR(nBytes-x)

	; do the right shift

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	; put (nBytes - shift) into DX

	xor eax,eax

	xor edx,edx

	mov ax,[nBytes]

	shl ax,3

	sub ax,[shift]

	mov dx,ax

	stdcall ShiftRight

	lea esi,[ShiftBf]

	stdcall OrIntoTempBuffer

	stdcall CopyFromTempBuffer

	jmp .DL_0

  .NO_ROT:

	stdcall TruncToNbytes

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Rotr_Dest_Immed uses esi edi ecx

  ; rot right dest by the number of bits given in immed

  ; ROTR dest immed

	locals

	  shift dw 0

	endl

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; check that nBytes is in range

	stdcall Check_nBytes

	; the number of bytes to apply the ROTR to is now given by nBytes

	; get the number of bits to shift by

	lea esi,[Immediate]

	mov edx,[esi]

	; if the number of bits to shift by is equal to (8 x nBytes) then no rot needed

	xor eax,eax

	mov ax,[nBytes]

	shl eax,3

	cmp eax,edx

	je .NO_ROT

	; if the number of bits to shift by is greater than (8 x nBytes)

	; calc EDX = EDX mod EAX

  .MOD:

	cmp edx,eax

	jl .B_COPY

	sub edx,eax

	cmp edx,eax

	; if (EDX mod EAX) = 0 then no rot needed

	je .NO_ROT

	jmp .MOD

  .B_COPY:

	mov [shift],dx

	; copy EDI to the shift buffer

	; ROTL(x) = SHL(x) or SHR(nBytes-x)

	; do the right shift

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	stdcall ShiftRight

	lea esi,[ShiftBf]

	; copy the result to the temp buffer

	stdcall CopyToTempBuffer

	; ROTL(x) = SHL(x) or SHR(nBytes-x)

	; do the left shift

	; clear the shift buffer

	stdcall ClearShiftBuffer

	; proc takes ESI

	mov esi,edi

	stdcall CopyToShiftBuffer

	; put (nBytes - shift) into DX

	xor eax,eax

	xor edx,edx

	mov ax,[nBytes]

	shl ax,3

	sub ax,[shift]

	mov dx,ax

	stdcall ShiftLeft

	lea esi,[ShiftBf]

	stdcall OrIntoTempBuffer

	stdcall CopyFromTempBuffer

	jmp .DL_0

  .NO_ROT:

	stdcall TruncToNbytes

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Check_nBytes uses eax

  ; check that nBytes is in range

	mov ax,[nBytes]

	cmp ax,0

	jle .ADJUST

	cmp ax,HEX_LEN

	jg .ADJUST

	jmp .DONE

  .ADJUST:

	mov [nBytes],HEX_LEN

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Trunc_Dest_Immed uses esi edi ecx

  ; truncate dest to the number of bits specified in immed

  ; TRUNC dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; get immed

	lea esi,[Immediate]

	; EAX gives the size of the trunc in bits

	mov eax,[esi]

	cmp eax,HEX_LEN * 8

	jge .DL_0

	; convert EAX to a byte index

	shr eax,3

	xor ecx,ecx

	mov cx,HEX_LEN-1

	cmp cx,ax

	je .BITS

  .TRUNC:

	mov [edi+ecx],byte 0

	dec cx

	cmp cx,ax

	jg .TRUNC

  .BITS:

	; deal with the remaining bits (if any)

	mov edx,[esi]

	and edx,7

	add edi,ecx

	mov cl,8
	sub cl,dl

	mov al,0xff

	shr al,cl

	and [edi],al

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Bset_Dest_Immed uses esi edi ecx

  ; in dest - turn on the number of bits specified in immed

  ; starting from bit 0

  ; BSET dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; get immed

	lea esi,[Immediate]

	mov eax,[esi]

	; convert to bytes

	shr eax,3

	cmp eax,0

	je .BITS

	; set the bytes

	mov cx,0

  .BSET:

	mov [edi],byte 0xff

	inc edi

	inc cx

	cmp cx,ax

	je .BITS

	cmp cx,HEX_LEN

	jge .DL_0

	jmp .BSET

  .BITS:

	mov eax,[esi]

	and eax,7

	cmp eax,0

	je .DL_0

	mov cl,8

	sub cl,al

	mov al,0xff

	shr al,cl

	or [edi],al

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mul_Dest_Immed uses esi edi ecx

  ; Multiply the dest by the immediate value:

  ; MULI dest immed

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; get the 16 byte immediate value

	lea esi,[Immediate]

	; also need an intermediate value - the temp buffer

	; clear the intermediate value

	stdcall ClearTempBuffer

	xor ecx,ecx

	xor edx,edx

  .MULTIPLY:

	; get a byte from ESI and multiply the hex value in EDI

	; add the result to the temp buffer

	mov al,[esi]

	mov ebx,edi

	; the byte is passed in AL, the offset in DL, EDI in EBX

	stdcall ByteMultiply

	inc esi

	inc dx

	inc cx

	cmp cx,16

	jl .MULTIPLY

	; copy the result from the temp buffer back to EDI

	stdcall CopyFromTempBuffer

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Div_Dest_Immed uses esi edi ecx

  ; Divide the dest value by the immediate value

  ; DIVI dest immed

	shr eax,8

	; dest index is in AL

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; reset the quotient and divisor

	stdcall Zero_Quot_Div

	; copy the immediate value to the divisor

	stdcall ImmediateToDivisor

	; do the division

	stdcall Divide

	; copy the quotient to the destination

	stdcall QuotientToDest

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Mod_Dest_Immed uses esi edi ecx

  ; compute dest mod immed

  ; MODI dest immed

	shr eax,8

	; dest index is in AL

	; get EDI for the dest arg

	mov dl,al

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; reset the quotient and divisor

	stdcall Zero_Quot_Div

	; copy the immediate value to the divisor

	stdcall ImmediateToDivisor

	; do the division

	stdcall Divide

	; remainder is in EDI (the destination)

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Not_Dest uses edi ecx

  ; NOT the destination value

  ; NOT dest

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; NOT the destination

	mov cx,0

  .NOT:

	not byte [edi]

	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .NOT

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Neg_Dest uses edi ecx

  ; NEG the destination value = NOT(dest) + 1

  ; NEG dest

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; NOT the destination

	xor ecx,ecx

  .NOT:

	not byte [edi+ecx]

	inc cx

	cmp cx,HEX_LEN

	jl .NOT

	; add 1 to the dest

	xor ecx,ecx

  .ADD_C:

	add [edi+ecx],byte 1

	jnc .DL_0

	inc cx

	cmp cx,HEX_LEN

	jl .ADD_C

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:


	ret

endp

; -------------------------------------------------------------------------------------

proc Clr_Dest uses edi

  ; CLR the destination value

  ; CLR dest

	; get EDI for the arg (dest)

	; arg index is in AH

	mov dl,ah

	stdcall GetDestPtr

	cmp dl,0xff

	je .DONE

	; clear the destination

	stdcall ClearParam

  .DL_0:

	; return DL = 0 (success)

	xor edx,edx

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc GetDestPtr

  ; the index of the arg is passed in DL

  ; set EDI to point to the arg

  ; error code returned in DL

	cmp dl,1
	je .1
	cmp dl,2
	je .2
	cmp dl,3
	je .3
	cmp dl,4
	je .4
	cmp dl,5
	je .5
	cmp dl,6
	je .6
	cmp dl,7
	je .7
	cmp dl,8
	je .8
	cmp dl,9
	je .9
	cmp dl,10
	je .10
	cmp dl,11
	je .11
	cmp dl,12
	je .12
	cmp dl,13
	je .13
	cmp dl,14
	je .14

	jmp .ERROR

  .1:

	lea edi,[Param_1]
	jmp .DONE

  .2:

	lea edi,[Param_2]
	jmp .DONE

  .3:

	lea edi,[Param_3]
	jmp .DONE

  .4:

	lea edi,[Param_4]
	jmp .DONE

  .5:

	lea edi,[Param_5]
	jmp .DONE

  .6:

	lea edi,[Param_6]
	jmp .DONE

  .7:

	lea edi,[Param_7]
	jmp .DONE

  .8:

	lea edi,[Param_8]
	jmp .DONE

  .9:

	lea edi,[Param_9]
	jmp .DONE

  .10:

	lea edi,[Param_10]
	jmp .DONE

  .11:

	lea edi,[Reg_1]
	jmp .DONE

  .12:

	lea edi,[Reg_2]
	jmp .DONE

  .13:

	lea edi,[Reg_3]
	jmp .DONE

  .14:

	lea edi,[Reg_4]
	jmp .DONE

  .ERROR:

	mov dl,0xff

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ClearShiftBuffer uses edi ecx

  ; clear ShiftBf

	lea edi,[ShiftBf]

	mov cx,0

  .CLEAR:

	mov [edi],byte 0

	inc edi

	inc cx

	cmp cx,HEX_LEN

	jl .CLEAR

	ret

endp

; -------------------------------------------------------------------------------------

proc CopyToShiftBuffer uses edi ecx

  ; copy nBytes from ESI to the shift buffer

  ; called by the SHL, SHR, ROTL, ROTR functions

  ; ESI is not modified

	lea edi,[ShiftBf]

	xor ecx,ecx

  .COPY:

	mov al,[esi+ecx]

	mov [edi+ecx],al

	inc cx

	cmp cx,[nBytes]

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc CopyFromShiftBuffer uses esi ecx

  ; copy to EDI from the shift buffer

  ; EDI is not modified

	lea esi,[ShiftBf]

	xor ecx,ecx

  .COPY:

	mov al,[esi+ecx]

	mov [edi+ecx],al

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc ClearTempBuffer uses edi ecx

  ; clear TempBf

	lea edi,[TempBf]

	xor ecx,ecx

  .CLEAR:

	mov [edi+ecx],byte 0

	inc cx

	cmp cx,HEX_LEN

	jl .CLEAR

	ret

endp

; -------------------------------------------------------------------------------------

proc CopyToTempBuffer uses edi ecx

  ; copy from ESI to the temp buffer

  ; ESI is not modified

	lea edi,[TempBf]

	xor ecx,ecx

  .COPY:

	mov al,[esi+ecx]

	mov [edi+ecx],al

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc CopyFromTempBuffer uses esi ecx

  ; copy to EDI from the temp buffer

  ; EDI is not modified

	lea esi,[TempBf]

	xor ecx,ecx

  .COPY:

	mov al,[esi+ecx]

	mov [edi+ecx],al

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc OrIntoTempBuffer uses edi ecx

  ; OR ESI into the temp buffer

  ; ESI is not modified

	lea edi,[TempBf]

	xor ecx,ecx

  .OR:

	mov al,[esi+ecx]

	or [edi+ecx],al

	inc cx

	cmp cx,HEX_LEN

	jl .OR

	ret

endp

; -------------------------------------------------------------------------------------

proc TruncToNbytes uses ecx

  ; truncate EDI to nBytes

	xor ecx,ecx

	mov cx,[nBytes]

  .TRUNC:

	cmp cx,HEX_LEN

	jge .DONE

	mov [edi+ecx],byte 0

	inc cx

	jmp .TRUNC

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Divide uses ecx

  ; divide EDI by the divisor

  ; the result is stored in Quotient

  ; remainder in EDI

  ; EDI is set outside of this procedure

	locals

	  offset dw 0

	  qdigit db 0

	endl

	stdcall IsDivisorZero

	cmp al,1

	; divide by zero error

	je .DIV_BY_ZERO

	; is the dividend zero (in EDI)

	stdcall IsZero

	cmp al,1

	je .DONE

	stdcall Is_GTE_Divisor

	cmp al,0

	; do nothing

	je .DONE

	; start the division by aligning the digits in the divisor with the dividend

	; put EDI into EAX for the proc

	mov eax,edi

	stdcall AlignDivisor

	; digit counts returned in EAX

	; EAX = |ndigits(src)|ndigits(dest)|

	cmp ax,0

	je .DONE

	mov [offset],ax

	shr eax,16

	cmp ax,0

	je .DONE

	; offset = ndigits(src) - ndigits(dest)

	sub [offset],ax

	cmp [offset],0

	jl .DONE

  .DIVIDE:

	mov [qdigit],byte 0

  .LT_DIV:

	; if EDI is less than the divisor, shift the divisor right by 1 digit

	stdcall Is_GTE_Divisor

	cmp al,1

	je .SUBTRACT

	stdcall ShiftDigitsRight

	dec word [offset]

	cmp [offset],word 0

	jl .DONE

	jmp .LT_DIV

  .SUBTRACT:

	stdcall SubtractDivisor

	inc byte [qdigit]

	stdcall Is_GTE_Divisor

	cmp al,1

	je .SUBTRACT

	mov al,[qdigit]

	shl eax,16

	mov ax,[offset]

	stdcall AddDigitToQuotient

	jmp .DIVIDE

  .DIV_BY_ZERO:

	mov [nExeErrorCode],byte 2

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc IsZero uses ecx

  ; is the hex value in EDI zero ?

  ; EDI set outside of this procedure

  ; result returned in AL

	xor eax,eax

	xor ecx,ecx

  .TEST:

	cmp [edi+ecx],byte 0

	jne .DONE

	inc cx

	cmp cx,HEX_LEN

	jl .TEST

	; number is zero, set AL = 1

	mov al,1

 .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Is_GTE_Divisor uses esi ecx

  ; compare the hex number in EDI to the divisor

  ; is EDI greater than or equal to ESI ?

  ; EDI is set outside of this procedure

  ; result returned in AL

	lea esi,[Divisor]

	xor eax,eax

	xor ecx,ecx

	mov cx,HEX_LEN-1

  .TEST:

	mov dl,[esi+ecx]

	cmp [edi+ecx],dl

	jb .DONE

	ja .GTE

	dec cx

	cmp cx,0

	jge .TEST

  .GTE:

	mov al,1

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Get_nDigits uses esi ecx

  ; get the number of hex digits in ESI

  ; ESI is passed in EAX

  ; result returned in AX

	mov esi,eax

	xor ecx,ecx

	mov cx,HEX_LEN-1

	xor eax,eax

	; AX will be in the range 0 - 800 (800 hex digits in a 400 byte number)

	mov ax,2*HEX_LEN

  .TEST:

	mov dl,[esi+ecx]

	test dl,0xf0

	jnz .DONE

	dec ax

	test dl,0x0f

	jnz .DONE

	dec ax

	dec cx

	cmp cx,0

	jge .TEST

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc Zero_Quot_Div uses edi ecx

  ; zero the quotient and divisor

	; quotient

	xor ecx,ecx

	lea edi,[Quotient]

  .ZQ:

	mov [edi+ecx],byte 0

	inc cx

	cmp cx,HEX_LEN

	jl .ZQ

	; divisor

	xor ecx,ecx

	lea edi,[Divisor]

  .ZD:

	mov [edi+ecx],byte 0

	inc cx

	cmp cx,HEX_LEN

	jl .ZD

	ret

endp

; -------------------------------------------------------------------------------------

proc AddDigitToQuotient uses edi ecx

  ; add a digit to the Quotient

  ; offset and digit passed in EAX

  ; offset is in nibbles (hex digits)

	lea edi,[Quotient]

	xor ecx,ecx

	; put the offset into cx

	mov cx,ax

	shr eax,16

	; the digit is now in AL

	test cx,1

	jnz .UPPER

  .LOWER:

	; SHR(1) to get the offset in bytes

	shr ecx,1

	or [edi+ecx],al

	jmp .DONE

  .UPPER:

	; SHR(1) to get the offset in bytes

	shr ecx,1

	shl al,4

	or [edi+ecx],al

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc IsDivisorZero uses edi

  ; is the divisor zero ?

	lea edi,[Divisor]

	stdcall IsZero

	ret

endp

; -------------------------------------------------------------------------------------

proc CopyToDivisor uses edi ecx

  ; copy the value in ESI to the divisor

  ; ESI is set outside of this procedure

  ; ESI is not modified

	lea edi,[Divisor]

	xor ecx,ecx

  .COPY:

	mov dl,[esi+ecx]

	mov [edi+ecx],dl

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc ImmediateToDivisor uses esi edi ecx

  ; copy the immediate value to the divisor

	lea esi,[Immediate]

	lea edi,[Divisor]

	xor ecx,ecx

  .COPY:

	mov dl,[esi+ecx]

	mov [edi+ecx],dl

	inc cx

	cmp cx,16

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc QuotientToDest uses esi ecx

  ; copy the quotient to EDI

  ; EDI is set outside of this procedure

	lea esi,[Quotient]

	xor ecx,ecx

  .COPY:

	mov dl,[esi+ecx]

	mov [edi+ecx],dl

	inc cx

	cmp cx,HEX_LEN

	jl .COPY

	ret

endp

; -------------------------------------------------------------------------------------

proc SubtractDivisor uses esi ecx

  ; subtract the divisor from EDI

  ; EDI is set outside of this procedure

	lea esi,[Divisor]

	xor ecx,ecx

	clc

	; push flags register to the stack

	pushf

  .SUB:

	mov dl,[esi+ecx]

	; get the flags register from the stack

	popf

	sbb [edi+ecx],dl

	; save the flags register to the stack, for the next iteration

	pushf

	inc cx

	; cmp affects the CF

	cmp cx,HEX_LEN

	jl .SUB

	; clean up the stack

	popf

	ret

endp

; -------------------------------------------------------------------------------------

proc AlignDivisor uses esi edi ecx

  ; align the divisor with the number in EDI

  ; EDI is passed in EAX

	locals

	  nDigits_Src dw 0

	  nDigits_Dest dw 0

	  shift dw 0

	  ncopy dw 0

	endl

	; get EDI

	mov edi,eax

	xor eax,eax

	lea esi,[Divisor]

	mov eax,esi

	stdcall Get_nDigits

	mov [nDigits_Src],ax

	cmp ax,0

	; number in ESI is zero, do nothing

	je .DONE

	mov eax,edi

	stdcall Get_nDigits

	mov [nDigits_Dest],ax

	cmp ax,0

	; number in EDI is zero, do nothing

	je .DONE

	cmp ax,[nDigits_Src]

	; the two numbers are already aligned or the divisor is greater than EDI

	; do nothing

	jle .DONE

	; compute the shift (the number of hex digits to shift the Divisor up by)

	sub ax,[nDigits_Src]

	mov [shift],ax

	; copy up the hex digits

	lea edi,[Divisor]

	; divide AX by 2 to get the number of bytes to shift up by

	shr ax,1

	cmp ax,0

	je .DIGIT

	; put the shift into EDX and sub from ESI

	xor edx,edx

	mov dx,ax

	sub esi,edx

	xor ecx,ecx

	mov cx,HEX_LEN-1

	; number of bytes to copy up

	mov word [ncopy],HEX_LEN

	sub [ncopy],dx

  .BYTES:

	; copy up the bytes

	mov dl,[esi+ecx]

	mov [edi+ecx],dl

	dec cx

	dec word [ncopy]

	cmp [ncopy],word 0

	jg .BYTES

	cmp cx,0

	jl .DIGIT

  .ZERO:

	; zero the rest of the bytes

	mov [edi+ecx],byte 0

	dec cx

	cmp cx,0

	jge .ZERO

  .DIGIT:

	mov ax,[shift]

	test ax,1

	jz .DONE

	; shift up by 1 more digit (4 bits)

	stdcall ShiftDigitsLeft

  .DONE:

	; return the digit counts in EAX

	mov ax,[nDigits_Src]

	shl eax,16

	mov ax,[nDigits_Dest]

	ret

endp

; -------------------------------------------------------------------------------------

proc ShiftDigitsLeft uses esi edi ecx

  ; shift the Divisor one digit to the left

	lea esi,[Divisor]

	lea edi,[Divisor]

	xor ecx,ecx

	mov cx,HEX_LEN-1

	add esi,ecx
	add edi,ecx

  .SHL:

	mov dl,[esi]

	shl dl,4

	mov [edi],dl

	cmp cx,0

	jle .DONE

	dec esi

	mov dl,[esi]

	shr dl,4

	or [edi],dl

	dec edi

	dec cx

	jmp .SHL

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

proc ShiftDigitsRight uses esi edi ecx

  ; shift the Divisor one digit to the right

	lea esi,[Divisor]

	lea edi,[Divisor]

	mov cx,0

  .SHR:

	mov dl,[esi]

	shr dl,4

	mov [edi],dl

	inc esi

	cmp cx,HEX_LEN-1

	jge .DONE

	mov dl,[esi]

	shl dl,4

	or [edi],dl

	inc edi

	inc cx

	jmp .SHR

  .DONE:

	ret

endp

; -------------------------------------------------------------------------------------

section '.idata' import data readable writeable

  library kernel,'KERNEL32.DLL',
	  user,'USER32.DLL'

  import kernel,
	 GetModuleHandle,'GetModuleHandleA',
	 ExitProcess,'ExitProcess'

  import user,
	 DialogBoxParam,'DialogBoxParamA', 
	 SetDlgItemText,'SetDlgItemTextA',
	 GetDlgItemText,'GetDlgItemTextA',
	 EndDialog,'EndDialog'

; -------------------------------------------------------------------------------------

section '.data' readable writeable

  bfDisplay rb 12000

  InputBuffer rb MAX_HEX_DIGITS + 8

  Param_1 rb HEX_LEN
  Param_2 rb HEX_LEN
  Param_3 rb HEX_LEN
  Param_4 rb HEX_LEN
  Param_5 rb HEX_LEN
  Param_6 rb HEX_LEN
  Param_7 rb HEX_LEN
  Param_8 rb HEX_LEN
  Param_9 rb HEX_LEN
  Param_10 rb HEX_LEN

  Reg_1 rb HEX_LEN
  Reg_2 rb HEX_LEN
  Reg_3 rb HEX_LEN
  Reg_4 rb HEX_LEN

  TempBf rb HEX_LEN

  ShiftBf rb HEX_LEN

  Quotient rb HEX_LEN
  Divisor rb HEX_LEN

  Symbol_1 rb 10
  Symbol_2 rb 10
  Symbol_3 rb 10
  Symbol_4 rb 10
  Symbol_5 rb 10
  Symbol_6 rb 10
  Symbol_7 rb 10
  Symbol_8 rb 10
  Symbol_9 rb 10
  Symbol_10 rb 10

  Calculations rb CALCS_BUFFER_SZ

  CurrentLine rb 100

  CurrentInstr rb 10

  CurrentArg rb 10

  bfImmediate rb 40

  Immediate rb 16

  nCalculation db 0

  nErrorCode db 0

  nExeErrorCode db 0

  nBytes dw HEX_LEN

  szInputText db "Define up to ten 400 byte input parameters here:",0

  szExprText db "Enter instructions here:",0

  szError_0 db "Unknown error in calculations input buffer.",0
  szError_1 db "Extra chars found at the end of the line:",0
  szError_2 db "Immediate value argument contains non hex digits in the line:",0
  szError_3 db "Missing argument in the line:",0
  szError_4 db "Could not match argument to symbol in the line:",0
  szError_5 db "Unknown instruction in the line:",0
  szError_6 db "Immediate value must be LTE to 3200 (decimal) in the line:",0
  szError_7 db "Invalid chars in calculations input buffer.",0
  szError_8 db "Invalid chars in hex parameters input buffer.",0

  szExeError_0 db "Unknown error - execution failed:",0
  szExeError_1 db "Unknown instruction or argument code:",0
  szExeError_2 db "Instruction failed - divide by zero error.",0

  Symbol_11 db "reg_1",0
  Symbol_12 db "reg_2",0
  Symbol_13 db "reg_3",0
  Symbol_14 db "reg_4",0

; -------------------------------------------------------------------------------------

section '.rc' resource data readable

  directory RT_DIALOG,dialogs

  resource dialogs,IDD_THE_DIALOG,LANG_ENGLISH+SUBLANG_DEFAULT,the_dialog

  dialog the_dialog,
  'FASM - Hex Calculator',20,50,522,396,
  DS_MODALFRAME+WS_MINIMIZEBOX+WS_POPUP+WS_VISIBLE+WS_CAPTION+WS_SYSMENU,
  0,0,"Lucida Console",11

  dialogitem 'EDIT',0,IDC_INPUT,0,0,260,366,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0
  dialogitem 'EDIT',0,IDC_CALCS,262,0,260,120,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0
  dialogitem 'EDIT',0,IDC_OUTPUT,262,122,260,244,ES_MULTILINE+ES_AUTOVSCROLL+ES_WANTRETURN+WS_VSCROLL+WS_BORDER+WS_VISIBLE,0

  dialogitem 'BUTTON',"Load Parameters",IDC_BTN_LOAD_DATA,7,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0
  dialogitem 'BUTTON',"Clear Parameters",IDC_BTN_CLR_DATA,89,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0
  dialogitem 'BUTTON',"Run Instructions",IDC_BTN_CALC,171,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0
  dialogitem 'BUTTON',"Clear Instructions",IDC_BTN_CLR_INSTR,253,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0
  dialogitem 'BUTTON',"Reset All",IDC_BTN_RESET_ALL,335,375,80,14,BS_PUSHBUTTON+WS_VISIBLE,0

  enddialog

; -------------------------------------------------------------------------------------

Понравилась статья? Поделить с друзьями:

Не пропустите и эти статьи:

  • Как написать калькулятор на vba
  • Как написать калькулятор на python код
  • Как написать калькулятор на python для начинающих
  • Как написать калькулятор на php
  • Как написать калькулятор на pascal

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии