; Calculator.asm


name "calc2"

; command prompt based simple calculator (+,-,*,/) for 8086. 
; example of calculation: 
; input 1 <- number:   10  
; input 2 <- operator: -  
; input 3 <- number:   5  
; -------------------  
;     10 - 5 = 5  
; output  -> number:   5 





;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 
;;; this maro is copied from emu8086.inc ;;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 
; this macro prints a char in AL and advances 
; the current cursor position: 
PUTC    MACRO   char
        PUSH    AX
        MOV     AL, char
        MOV     AH, 0Eh
        INT     10h
        POP     AX
ENDM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 





org 100h

jmp start


; define variables: 

msg0 db "note: calculator works with integer values only.",0Dh,0Ah
     db "to learn how to output the result of a float division see float.asm in examples",0Dh,0Ah,'$'
msg1 db 0Dh,0Ah, 0Dh,0Ah, 'enter first number: $'
msg2 db "enter the operator:    +  -  *  /     : $"
msg3 db "enter second number: $"
msg4 db  0dh,0ah , 'the approximate result of my calculations is : $'
msg5 db  0dh,0ah ,'thank you for using the calculator! press any key... ', 0Dh,0Ah, '$'
err1 db  "wrong operator!", 0Dh,0Ah , '$'
smth db  " and something.... $"

; operator can be: '+','-','*','/' or 'q' to exit in the middle. 
opr db '?'

; first and second number: 
num1 dw ?
num2 dw ?


start:
mov dx, offset msg0
mov ah, 9
int 21h


lea dx, msg1
mov ah, 09h    ; output string at ds:dx 
int 21h


; get the multi-digit signed number 
; from the keyboard, and store 
; the result in cx register: 

call scan_num

; store first number: 
mov num1, cx



; new line: 
putc 0Dh
putc 0Ah




lea dx, msg2
mov ah, 09h     ; output string at ds:dx 
int 21h


; get operator: 
mov ah, 1   ; single char input to AL. 
int 21h
mov opr, al



; new line: 
putc 0Dh
putc 0Ah


cmp opr, 'q'      ; q - exit in the middle. 
je exit

cmp opr, '*'
jb wrong_opr
cmp opr, '/'
ja wrong_opr






; output of a string at ds:dx 
lea dx, msg3
mov ah, 09h
int 21h


; get the multi-digit signed number 
; from the keyboard, and store 
; the result in cx register: 

call scan_num


; store second number: 
mov num2, cx




lea dx, msg4
mov ah, 09h      ; output string at ds:dx 
int 21h




; calculate: 





cmp opr, '+'
je do_plus

cmp opr, '-'
je do_minus

cmp opr, '*'
je do_mult

cmp opr, '/'
je do_div


; none of the above.... 
wrong_opr:
lea dx, err1
mov ah, 09h     ; output string at ds:dx 
int 21h


exit:
; output of a string at ds:dx 
lea dx, msg5
mov ah, 09h
int 21h


; wait for any key... 
mov ah, 0
int 16h


ret  ; return back to os. 











do_plus:


mov ax, num1
add ax, num2
call print_num    ; print ax value. 

jmp exit



do_minus:

mov ax, num1
sub ax, num2
call print_num    ; print ax value. 

jmp exit




do_mult:

mov ax, num1
imul num2 ; (dx ax) = ax * num2.  
call print_num    ; print ax value. 
; dx is ignored (calc works with tiny numbers only). 

jmp exit




do_div:
; dx is ignored (calc works with tiny integer numbers only). 
mov dx, 0
mov ax, num1
idiv num2  ; ax = (dx ax) / num2. 
cmp dx, 0
jnz approx
call print_num    ; print ax value. 
jmp exit
approx:
call print_num    ; print ax value. 
lea dx, smth
mov ah, 09h    ; output string at ds:dx 
int 21h
jmp exit









;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 
;;; these functions are copied from emu8086.inc ;;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 


; gets the multi-digit SIGNED number from the keyboard, 
; and stores the result in CX register: 
SCAN_NUM        PROC    NEAR
        PUSH    DX
        PUSH    AX
        PUSH    SI

        MOV     CX, 0

        ; reset flag: 
        MOV     CS:make_minus, 0

next_digit:

        ; get char from keyboard 
        ; into AL: 
        MOV     AH, 00h
        INT     16h
        ; and print it: 
        MOV     AH, 0Eh
        INT     10h

        ; check for MINUS: 
        CMP     AL, '-'
        JE      set_minus

        ; check for ENTER key: 
        CMP     AL, 0Dh  ; carriage return? 
        JNE     not_cr
        JMP     stop_input
not_cr:


        CMP     AL, 8                   ; 'BACKSPACE' pressed? 
        JNE     backspace_checked
        MOV     DX, 0                   ; remove last digit by 
        MOV     AX, CX                  ; division: 
        DIV     CS:ten                  ; AX = DX:AX / 10 (DX-rem). 
        MOV     CX, AX
        PUTC    ' '                     ; clear position. 
        PUTC    8                       ; backspace again. 
        JMP     next_digit
backspace_checked:


        ; allow only digits: 
        CMP     AL, '0'
        JAE     ok_AE_0
        JMP     remove_not_digit
ok_AE_0:
        CMP     AL, '9'
        JBE     ok_digit
remove_not_digit:
        PUTC    8       ; backspace. 
        PUTC    ' '     ; clear last entered not digit. 
        PUTC    8       ; backspace again.         
        JMP     next_digit ; wait for next input.        
ok_digit:


        ; multiply CX by 10 (first time the result is zero) 
        PUSH    AX
        MOV     AX, CX
        MUL     CS:ten                  ; DX:AX = AX*10 
        MOV     CX, AX
        POP     AX

        ; check if the number is too big 
        ; (result should be 16 bits) 
        CMP     DX, 0
        JNE     too_big

        ; convert from ASCII code: 
        SUB     AL, 30h

        ; add AL to CX: 
        MOV     AH, 0
        MOV     DX, CX      ; backup, in case the result will be too big. 
        ADD     CX, AX
        JC      too_big2    ; jump if the number is too big. 

        JMP     next_digit

set_minus:
        MOV     CS:make_minus, 1
        JMP     next_digit

too_big2:
        MOV     CX, DX      ; restore the backuped value before add. 
        MOV     DX, 0       ; DX was zero before backup! 
too_big:
        MOV     AX, CX
        DIV     CS:ten  ; reverse last DX:AX = AX*10, make AX = DX:AX / 10 
        MOV     CX, AX
        PUTC    8       ; backspace. 
        PUTC    ' '     ; clear last entered digit. 
        PUTC    8       ; backspace again.         
        JMP     next_digit ; wait for Enter/Backspace. 


stop_input:
        ; check flag: 
        CMP     CS:make_minus, 0
        JE      not_minus
        NEG     CX
not_minus:

        POP     SI
        POP     AX
        POP     DX
        RET
make_minus      DB      ?       ; used as a flag. 
SCAN_NUM        ENDP





; this procedure prints number in AX, 
; used with PRINT_NUM_UNS to print signed numbers: 
PRINT_NUM       PROC    NEAR
        PUSH    DX
        PUSH    AX

        CMP     AX, 0
        JNZ     not_zero

        PUTC    '0'
        JMP     printed

not_zero:
        ; the check SIGN of AX, 
        ; make absolute if it's negative: 
        CMP     AX, 0
        JNS     positive
        NEG     AX

        PUTC    '-'

positive:
        CALL    PRINT_NUM_UNS
printed:
        POP     AX
        POP     DX
        RET
PRINT_NUM       ENDP



; this procedure prints out an unsigned 
; number in AX (not just a single digit) 
; allowed values are from 0 to 65535 (FFFF) 
PRINT_NUM_UNS   PROC    NEAR
        PUSH    AX
        PUSH    BX
        PUSH    CX
        PUSH    DX

        ; flag to prevent printing zeros before number: 
        MOV     CX, 1

        ; (result of "/ 10000" is always less or equal to 9). 
        MOV     BX, 10000       ; 2710h - divider. 

        ; AX is zero? 
        CMP     AX, 0
        JZ      print_zero

begin_print:

        ; check divider (if zero go to end_print): 
        CMP     BX,0
        JZ      end_print

        ; avoid printing zeros before number: 
        CMP     CX, 0
        JE      calc
        ; if AX<BX then result of DIV will be zero: 
        CMP     AX, BX
        JB      skip
calc:
        MOV     CX, 0   ; set flag. 

        MOV     DX, 0
        DIV     BX      ; AX = DX:AX / BX   (DX=remainder). 

        ; print last digit 
        ; AH is always ZERO, so it's ignored 
        ADD     AL, 30h    ; convert to ASCII code. 
        PUTC    AL


        MOV     AX, DX  ; get remainder from last div. 

skip:
        ; calculate BX=BX/10 
        PUSH    AX
        MOV     DX, 0
        MOV     AX, BX
        DIV     CS:ten  ; AX = DX:AX / 10   (DX=remainder). 
        MOV     BX, AX
        POP     AX

        JMP     begin_print

print_zero:
        PUTC    '0'

end_print:

        POP     DX
        POP     CX
        POP     BX
        POP     AX
        RET
PRINT_NUM_UNS   ENDP



ten             DW      10      ; used as multiplier/divider by SCAN_NUM & PRINT_NUM_UNS. 







GET_STRING      PROC    NEAR
PUSH    AX
PUSH    CX
PUSH    DI
PUSH    DX

MOV     CX, 0                   ; char counter. 

CMP     DX, 1                   ; buffer too small? 
JBE     empty_buffer            ; 

DEC     DX                      ; reserve space for last zero. 


;============================ 
; Eternal loop to get 
; and processes key presses: 

wait_for_key:

MOV     AH, 0                   ; get pressed key. 
INT     16h

CMP     AL, 0Dh                  ; 'RETURN' pressed? 
JZ      exit_GET_STRING


CMP     AL, 8                   ; 'BACKSPACE' pressed? 
JNE     add_to_buffer
JCXZ    wait_for_key            ; nothing to remove! 
DEC     CX
DEC     DI
PUTC    8                       ; backspace. 
PUTC    ' '                     ; clear position. 
PUTC    8                       ; backspace again. 
JMP     wait_for_key

add_to_buffer:

        CMP     CX, DX          ; buffer is full? 
        JAE     wait_for_key    ; if so wait for 'BACKSPACE' or 'RETURN'... 

        MOV     [DI], AL
        INC     DI
        INC     CX

        ; print the key: 
        MOV     AH, 0Eh
        INT     10h

JMP     wait_for_key
;============================ 

exit_GET_STRING:

; terminate by null: 
MOV     [DI], 0

empty_buffer:

POP     DX
POP     DI
POP     CX
POP     AX
RET
GET_STRING      ENDP






; - other assembly language source codes -



; - asm2html by emu8086 -