; Dec_IO.asm  (sometimes named Dec_IO.pkg)
;  package of 3 assembler procedures for
;  performing standard decimal IO with the keyboard
;  and a DOS window by Alan T. Pinck
;
;    getNum accepts keyboard input and assuming
;       decimal characters representing a number
;       in the range 0 to 9999, converts the input
;       into a binary number which is returned in
;       the AX register.  Leading blanks and carriage
;       return codes are skipped.
;       getNum returns with the Carry flag off if
;       it was successful and with the Carry flag on
;       if it was not (if the keyboard input did not
;       start with something that could be interpretted
;       as a number, or if the number exceeds 4 digits)
;
;    getOp accepts keyboard input and returns the first
;       non-blank, non-carriage return character entered in AL
;
;    showNum displays the contents of AX (assumed to be
;       a number in 2's complement form) as a decimal value


;--- data which is local to this package ---
; (do not use these names in your own program!)
lastchar db     00
newline  db     0Dh,0Ah,"$"
num      dw      ?
; ===========================================


; showNum displays the 2's complement number
;  in AX as a decimal number

showNum proc    near
        push    ax     ;save working registers
        push    bx
        push    cx
        push    dx

        push    ax
        mov     dx,offset newline ;ensure display is on a newline
        mov     ah,09
        int     21h
        pop     ax
        
        cmp     ax,0    ;test for negative
        jge     positive;skip ahead if positive
        mov     ah,02   ;display character code
        mov     dl,'-'  ;  minus sign
        int     21h
        mov     bx,ax
        sub     ax,bx   ;ax now zero
        sub     ax,bx   ;ax now positive

positive:        
        mov     bx,10   ;divide by 10 to convert to decimal
        mov     cl,0    ;count of number of digits produced
div_loop:
        mov     dx,0    ;setup to divide dx|ax by bx
        idiv    bx      ;unsigned division: 
                        ;quotient in ax, remainder in dx
        push    dx      ;save the digit (remainder)
        inc     cl      ;keep track of number of digits
        cmp     ax,0    ;until nothing left to divide
        jnz     div_loop;  loop back

show_digit:
        pop     dx
        add     dl,30h  ;convert digit to ASCII code
        mov     ah,02   ;display character
        int     21h
        dec     cl      ;loop until all digits shown
        jnz     show_digit

        pop     dx      ;restore working registers
        pop     cx
        pop     bx
        pop     ax
        ret
showNum	endp

;==========================================

; getNum returns number input (converted to
;   binary) in ax
; input is assumed to be a positive, decimal
;   value with a maximum of 4 digits, terminated
;   with an ASCII non-digit code
; Carry flag is set (on) if an input error is
;   detected (Carry flag is clear (off) if the
;   input is valid

getNum  proc    near
        push    bx     ;save working registers
        push    cx
        push    dx
        
        mov     num,0
        mov     bh,'9'  ;setup for valid range testing
        mov     bl,'0'
        mov     cl,4    ;maximum number of digits acceptable
        
        mov     al,lastchar
        cmp     al,00   ;check if we no previous char available
        jne     gotchar ; skip input if one already here
skipwhite:
        mov     ah,01   ;input a char
        int     21h
gotchar:
        mov     lastchar,al ;skip over leading blanks and 
        cmp     al,20h      
        je      skipwhite
        cmp     al,0Dh      ;carriage returns
        jne     notwhite
        mov     ah,02       ;but force a "newline" if carriage
        mov     dl,0Ah      ; return
        int     21h
        jmp     skipwhite

notwhite:
; check initial to ensure number
        cmp     bh,al   ;check that it's < '9'
        jc      bad_digit ;error if its not
        sub     al,bl
        jc      bad_digit ;error if its < '0'

get_loop:
        mov     ah,0
        push    ax
        mov     ax,num
        mov     dx,10   ;decimal based system
        imul    dx      ;multiply ax by dx results in dx|ax
        mov     num,ax  ;value of prior digits times 10
        pop     ax      ;add this digit's value
        add     num,ax

        mov     ah,01   ;input a character
        int     21h
        mov     lastchar,al ;save it incase we're finished digits
        cmp     bh,al       ;exit getting digits if not a digit
        jc      get_exit
        sub     al,bl
        jc      get_exit
        
        dec     cl          ;if this is not more than
        jnz     get_loop    ;  4 digits add this digit to num
        sub     cl,1        ;otherwise exit with carry set
        jmp     bad_digit
        
get_exit:
        mov     ax,num  ; "good" number
        cmp     ax,ax   ; clear carry
bad_digit:  ;-- if we get here something is wrong
            ;-- by careful coding we have ensured that
            ;-- the carry flag will be set
            ;-- this is the "error" return indicator
        pop     dx  ;restore working registers
        pop     cx
        pop     bx
        ret

getNum  endp
;==========================================


; getOp returns the next char which is not
;   a blank and not a carriage return
;   as an ASCII code in AL

getOp   proc    near
        push    dx         ;save working register
        mov     al,lastchar ;check if char pre-read
        cmp     al,00
        je      getOpchar
getOploop:
        cmp     al,20h     ;skip leading blanks
        je      getOpchar
        cmp     al,0Dh     ;and carriage returns 
        je      getOpnewline
        jmp     getOpdone
getOpnewline:
        mov     ah,02      ;but output a newline if 
        mov     dl,0Ah     ; carriage return
        int     21h
getOpchar:
        mov     ah,01      ;get character
        int     21h
        mov     lastchar,al
        jmp     getOploop
getOpdone:
        mov     lastchar,00 ;no extra char read
        pop     dx          ;restore work register
        ret
getOp   endp

