;--------------------------------------------------------------------;
;  COMPUTE * PC Magazine * Michael J. Mefford                        ;
;  Command line five function math calculator.                       ;
;--------------------------------------------------------------------;

_TEXT          SEGMENT PUBLIC 'CODE'
               ASSUME  CS:_TEXT,DS:_TEXT,ES:_TEXT,SS:_TEXT
               ORG     100H
START:         JMP     MAIN


;              DATA AREA
;              ---------
SIGNATURE      DB      CR,SPACE,SPACE,SPACE,CR,LF

COPYRIGHT      DB      "COMPUTE 1.0 (C) 1990 Ziff Communications Co.",CR,LF
PROGRAMMER     DB      "PC Magazine ",BOX," Michael J. Mefford",CR,LF,LF,"$"
SYNTAX         DB      "Syntax: COMPUTE arithmetic expression",CR,LF
               DB      "Operators supported are + - * / % ( ) [ ] 0-9 x",CR,LF
               DB      "Note: % = modulo, remainder of integer division.",CR,LF
               DB      "      x = answer of last compute."
CRLFLF         DB      LF
CRLF           DB      CR,LF
               DB      "$",CTRL_Z

CR             EQU     13
LF             EQU     10
CTRL_Z         EQU     26
SPACE          EQU     32
BOX            EQU     254
COMMA          EQU     ","

INTEGER_PRECISION      =    20
DECIMAL_PRECISION      =    20
PRECISION      =       INTEGER_PRECISION + DECIMAL_PRECISION

NUMBER         STRUC
SIGN           DB      0
INTEGER        DB      INTEGER_PRECISION DUP (0)
DECIMAL        DB      DECIMAL_PRECISION DUP (0)
NUMBER         ENDS

SIZE_MANTISSA  =       SIZE INTEGER + SIZE DECIMAL
SIZE_DIVIDEND  =       SIZE_MANTISSA + SIZE DECIMAL + 1

OPERAND        DW      INITIAL_VALUE
OPERATORS      DB      "+-"
UNARY_PREFIXES LABEL   BYTE
PRECEDENCE     DB      "*/%"
PRECEDENCE_CNT =       $ - PRECEDENCE
OPERATOR_CNT   =       $ - OPERATORS
               DB      "([",0
PREFIXES_CNT   =       $ - UNARY_PREFIXES

LAST_CHAR      DB      ?

PAREN_CNT      DW      0
PAREN_TYPE     DB      50 DUP (0)
WORKSPACE      DB      2 * SIZE_MANTISSA DUP (?)
WORKSPACE_END  EQU     $ - 1
               DB      ?                       ;Dividend overflow.
DIVIDEND       DB      SIZE_DIVIDEND DUP (?)
ANSWER         DB      SIZE NUMBER DUP (0)

DECIMAL_PLACES DW      0                       ;Significant decimal digits.
SAVE_SIGN      DB      ?
MODULO_FLAG    DB      0                       ; =1 if modulo division.

COMPUTE        DB      "COMPUTE.COM",0
COMPUTE_LEN    EQU     $ - COMPUTE
PATH           DB      "PATH="
PATH_LEN       EQU     $ - PATH
NOT_FOUND      DB      CR,LF,"Could not save answer in Compute.com",CR,LF
               DB      "Compute.com must be in current directory",CR,LF
               DB      "or in DOS PATH= directory.",CR,LF,"$"
RESULT_MSG     DB      "Result is $"
UNRECOGNIZED   DB      "Unrecognized character$"
MISSING_PAREN  DB      "Missing parenthesis or bracket$"
UNMATCHED      DB      "Miss-matched parenthesis with bracket$"
ZERO_DIVIDE    DB      "Divide by zero$"
OVERFLOW       DB      "Overflow$"
LOGICAL        DB      "Logic error$"
MEMORY         DB      "Requires 10K of memory$"
ERROR1         DB      "Error ^",CR,LF,"$"
ERROR2         DB      "^ Error$",CR,LF,"$"


;              CODE AREA
;              ---------
MAIN           PROC    NEAR
               CLD                             ;Strings forward.

               MOV     DX,OFFSET SIGNATURE     ;Announce ourselves.
               CALL    PRINT_STRING

               MOV     BX,10 / 16 * 1024       ;Minimum of 10K required.
               MOV     AH,4AH                  ;Request via DOS.
               INT     21H
               MOV     DX,OFFSET MEMORY
               JC      ERROR_EXIT              ;Exit with message if not enough.

               MOV     SP,16 * 1024 - 2        ;Else, setup stack at end of seg.

               MOV     SI,81H                  ;Point to command line.
               XOR     AX,AX                   ;No characters yet.
               XOR     DX,DX                   ;Not unary.
               CALL    PARSE                   ;Parse command line.
               CALL    PARSE_DELIMIT           ;See if reached end.
               MOV     DX,OFFSET LOGICAL       ;If not, logical error.
               JNC     ERROR_EXIT

               CMP     OPERAND,OFFSET INITIAL_VALUE
               JNZ     CK_PARENS
               MOV     DX,OFFSET SYNTAX        ;Did we get a value.
               CALL    PRINT_STRING            ;If not, print syntax and exit.
               JMP     SHORT ERROR_END

CK_PARENS:     MOV     DX,OFFSET MISSING_PAREN
               CMP     PAREN_CNT,0             ;Did we match all parenthesis
               JNZ     ERROR_EXIT              ; and brackets?

               CALL    RESULT                  ;Every thing seems OK so
                                               ; display result.
EXIT:          MOV     AX,4C00H                ;Exit with ErrorLevel=0
               INT     21H
MAIN           ENDP

;----------------------------------------------;
ERROR_EXIT:    PUSH    DX                      ;Save error message.
               MOV     DX,OFFSET SYNTAX        ;Display syntax.
               CALL    PRINT_STRING
               POP     DX
               CALL    PRINT_STRING            ;Display error.
               MOV     DX,OFFSET CRLFLF
               CALL    PRINT_STRING
               MOV     BX,SI                   ;Current command line pointer.
               MOV     SI,81H                  ;Point at start of command line.
               CLD                             ;Make strings are forward.

NEXT_LINE:     MOV     CX,79                   ;1 less than 80 char. display.
NEXT_ERROR:    LODSB
               CMP     AL,CR                   ;End of command line?
               JZ      PRINT_POINTER
               CALL    PRINT_CHAR              ;If no, print character.
               CMP     SI,BX                   ;Pointing to error position?
               JZ      PRINT_POINTER           ;If yes, done here.
               LOOP    NEXT_ERROR              ;Else, continue.
               MOV     DX,OFFSET CRLF          ;Filled on line; new line.
               CALL    PRINT_STRING
               JMP     NEXT_LINE

PRINT_POINTER: MOV     DX,OFFSET CRLF          ;New line.
               CALL    PRINT_STRING
               SUB     CX,79                   ;Calculate chars. on line above.
               NEG     CX
               MOV     BX,OFFSET ERROR1        ;Assume left side of display.
               CMP     CX,40
               JB      LEFT_ARROW
               SUB     CX,6
               JMP     SHORT NEXT_SPACE
LEFT_ARROW:    MOV     BX,OFFSET ERROR2

NEXT_SPACE:    MOV     AL,SPACE                ;Pad with spaces over to error.
               CALL    PRINT_CHAR
               LOOP    NEXT_SPACE
               MOV     DX,BX                   ;Display "Error ^".
               CALL    PRINT_STRING

ERROR_END:     MOV     AX,4C01H                ;Exit with ErrorLevel = 1.
               INT     21H

;----------------------------------------------;
; Recursive parsing routine.                   ;
;----------------------------------------------;
PARSE:         PUSH    BP                      ;Preserve stack index.
               PUSH    DX                      ;Unary flag.
               PUSH    AX                      ;Operator.
               MOV     BP,SP                   ;Point to local vars.
NEXT_PARSE:    CALL    PARSE_DELIMIT           ;Parse white space.
               JNC     GET_CHAR                ;If command line end, exit.
               JMP     PARSE_END

GET_CHAR:      LODSB                           ;Else, get the character.
               MOV     DI,OFFSET OPERATORS     ;Is it a function operator?
               MOV     CX,OPERATOR_CNT
               REPNZ   SCASB
               JNZ     CK_NUM                  ;If no, check if number.

               XOR     DL,DL                   ;Assume not unary.
               CMP     AL,"+"                  ;Is it plus or minus?
               JZ      CK_UNARY
               CMP     AL,"-"
               JNZ     GET_OPERAND2            ;If no, not unary.
CK_UNARY:      PUSH    AX
               MOV     AL,LAST_CHAR            ;Else, does previous char
               MOV     DI,OFFSET UNARY_PREFIXES  ; suggest unary?
               MOV     CX,PREFIXES_CNT
               REPNZ   SCASB
               POP     AX
               JNZ     GET_OPERAND2
               INC     DL                      ;If yes, it's unary.

GET_OPERAND2:  PUSH    WORD PTR LAST_CHAR      ;Save last char.
               MOV     LAST_CHAR,AL            ;Store current char.
               PUSH    OPERAND                 ;Save operand pointer.
               CALL    PARSE                   ;Get second operand.
               POP     BX                      ;First operand.
               POP     CX                      ;Last character.
               MOV     DH,AL                   ;Save operator.
               MOV     DI,OPERAND              ;Second operand.
               MOV     AL,[DI]                 ;Get signs.
               MOV     AH,[BX]
               CMP     DH,"*"                  ;Check which operator.
               JZ      DO_MULT
               CMP     DH,"/"
               JZ      DO_DIV
               CMP     DH,"%"
               JZ      DO_MOD
                                               ;Must be plus or minus.
               OR      DL,DL                   ;Was it unary.
               JZ      DO_ADD                  ;If no, add operands.
               MOV     AL,CL                   ;Else, was last character
               CALL    CK_PRECEDENCE           ; "*", "/" or "%" ?
               JNZ     NEXT_PARSE              ;If no, just continue parsing.
               JMP     PARSE_END               ;Else, return second operand.

DO_ADD:        CALL    ADD_IT                  ;Add operands.
               MOV     AL,LAST_CHAR
               CMP     AL,")"                  ;Is this an equantity?
               JZ      PARSE_DONE              ;Is yes, return it.
               CMP     AL,"]"
               JZ      PARSE_DONE
               JMP     NEXT_PARSE              ;Else, continue parsing.

DO_MULT:       CALL    MULTIPLY
               JMP     NEXT_PARSE

DO_DIV:        CALL    DIVIDE
               JMP     NEXT_PARSE

DO_MOD:        CALL    MODULO
               JMP     NEXT_PARSE

PARSE_DONE:    JMP     PARSE_END               ;Lilly pad for return.

CK_NUM:        MOV     LAST_CHAR,AL            ;Store current character.
               CMP     AL,"X"                  ;Is it "answer" character?
               JZ      DO_ANS
               CMP     AL,"x"
               JNZ     CK_DIGIT
DO_ANS:        PUSH    SI
               MOV     SI,OFFSET ANSWER        ;If yes copy answer to operand.
               ADD     OPERAND,SIZE NUMBER     ;Point to next operand storage.
               MOV     DI,OPERAND
               PUSH    DI
               MOV     CX,SIZE NUMBER
               REP     MOVSB
               POP     DI
               MOV     AL,[BP]                 ;Get sign
               CMP     AL,"-"
               JZ      ANS_SIGN
               XOR     AL,AL                   ; and store.
ANS_SIGN:      XOR     [DI],AL
               POP     SI
               JMP     SHORT CK_UNARY_NUM      ;Check if return or not.

CK_DIGIT:      CMP     AL,"."                  ;Is char a num char?
               JZ      DO_NUM
               CMP     AL,"0"
               JB      CK_PAREN
               CMP     AL,"9"
               JA      CK_PAREN
DO_NUM:        DEC     SI                      ;If yes, point to it.
               ADD     OPERAND,SIZE NUMBER     ;Point to next operand storage.
               CALL    GET_NUMBER              ;Get the number.
CK_UNARY_NUM:  CMP     BYTE PTR [BP + 2],1     ;Is this a unary number?
               JZ      PARSE_END               ;If yes, return the number.
               MOV     AL,[BP]                 ;Get operator.
               CALL    CK_PRECEDENCE           ;If high precedence, go do it.
               JZ      PARSE_END
               JMP     NEXT_PARSE              ;Else, continue parsing.

CK_PAREN:      MOV     AH,")"
               CMP     AL,"("                  ;Is it an open parenthesis
               JZ      FOUND_PAREN             ; or bracket?
               CMP     AL,AH
               JZ      CK_OPEN
               MOV     AH,"]"
               CMP     AL,"["
               JZ      FOUND_PAREN
               CMP     AL,AH                   ;Was it a close parenthesis or
               MOV     DX,OFFSET UNRECOGNIZED  ; or bracket?
               JNZ     PARSE_ERROR             ;If no, must be invalid char.

CK_OPEN:       DEC     PAREN_CNT               ;If close, decrement level.
               MOV     DX,OFFSET MISSING_PAREN ;If overflow then had no
               JC      PARSE_ERROR             ; open.
               MOV     BX,PAREN_CNT            ;Retrieve level index
               MOV     PAREN_TYPE[BX],AH       ; and store close type.
               JMP     SHORT PARSE_END         ;Return results.

FOUND_PAREN:   PUSH    PAREN_CNT               ;Save level index.
               INC     PAREN_CNT               ;Next level.
               XOR     DX,DX                   ;Not unary flag.
               CALL    PARSE                   ;Parse the equantity.
               POP     BX                      ;Retrieve level.
               CMP     AH,PAREN_TYPE[BX]       ;Did we close with matching
               MOV     DX,OFFSET UNMATCHED     ; parenthesis or bracket?
               JNZ     PARSE_ERROR
               MOV     AL,[BP]                 ;Retrieve, prefix operator.
               CALL    CK_PRECEDENCE           ;Was it priority operator?
               JZ      PARSE_END               ;If yes, return value.
               CMP     AL,"-"                  ;Else store sign.
               JNZ     PAREN_END
               MOV     BX,OPERAND
               XOR     [BX],AL
PAREN_END:     JMP     NEXT_PARSE              ;And parse next.

PARSE_END:     POP     AX
               POP     DX
               POP     BP
               RET

PARSE_ERROR:   JMP     ERROR_EXIT
               RET

;-------------------------------------------------------------------;
; INPUT: SI -> string                                               ;
; OUTPUT SI -> first non-white space and CY=0 or SI -> CR and CY=1. ;
;-------------------------------------------------------------------;
PARSE_DELIMIT: LODSB                           ;Get a byte.
               CMP     AL,CR
               JZ      STRING_END
               CMP     AL,SPACE                ;Is it a space char or below?
               JBE     PARSE_DELIMIT
               DEC     SI                      ;Else, adjust pointer to
               CLC
               RET                             ; string start.

STRING_END:    DEC     SI
               STC
               RET

;----------------------------------------------;
CK_PRECEDENCE: MOV     DI,OFFSET PRECEDENCE
               MOV     CX,PRECEDENCE_CNT
               REPNZ   SCASB
               RET

;----------------------------------------------;
GET_NUMBER:    MOV     DI,OFFSET WORKSPACE     ;Store integer temporarily
               MOV     CX,SIZE INTEGER + 1     ; so can store right justified.
NEXT_INTEGER:  LODSB
               CMP     AL,COMMA                ;Ignore commas.
               JZ      NEXT_INTEGER
               CMP     AL,"."                  ;Decimal point; end of integer.
               JZ      STORE_INTEGER

GET_NUM2:      SUB     AL,"0"
               JC      INTEGER_END
               CMP     AL,9
               JA      INTEGER_END
               STOSB                           ;Store the number.
               LOOP    NEXT_INTEGER
               MOV     DX,OFFSET OVERFLOW      ;If got here, number too big.
               JMP     ERROR_EXIT

INTEGER_END:   DEC     SI
STORE_INTEGER: PUSH    SI                      ;Save command line pointer.
               MOV     SI,DI
               DEC     SI                      ;End of workspace.
               PUSHF
               STD
               MOV     DI,OPERAND
               ADD     DI,SIZE INTEGER         

               MOV     DX,CX
               DEC     DX                      ;Zero pad count.

               NEG     CX
               ADD     CX,SIZE INTEGER + 1
               JCXZ    PAD_INTEGER
               REP     MOVSB                   ;Store integer part in operand.

PAD_INTEGER:   MOV     CX,DX
               JCXZ    INTEGER_DONE
               XOR     AL,AL
               REP     STOSB                   ;Pad balance of integer with 0.
INTEGER_DONE:  MOV     AL,[BP]                 ;Get sign
               CMP     AL,"-"
               JZ      INT_SIGN
               XOR     AL,AL                   ; and store.
INT_SIGN:      STOSB
               POPF


DO_DECIMAL:    POP     SI                      ;Restore command line pointer.
               MOV     DI,OPERAND
               ADD     DI,SIZE SIGN + SIZE INTEGER   ;Decimal operand storage.
               MOV     CX,SIZE DECIMAL + 1           ;Left justified.
               XOR     DX,DX                   ;Decimal counter.
NEXT_DECIMAL:  LODSB
               SUB     AL,"0"
               JC      DECIMAL_END
               CMP     AL,9
               JA      DECIMAL_END
               STOSB
               INC     DX                      ;Increment decimal count.
               LOOP    NEXT_DECIMAL
               MOV     DX,OFFSET OVERFLOW      ;If got here, number too big.
               JMP     ERROR_EXIT

DECIMAL_END:   DEC     SI                      ;Adjust command line pointer.
               CMP     DX,DECIMAL_PLACES       ;Store significant decimal
               JBE     PAD_DECIMAL             ; places.
               MOV     DECIMAL_PLACES,DX

PAD_DECIMAL:   DEC     CX                      ;Pad balance with zeros.
               JCXZ    DECIMAL_DONE
               XOR     AL,AL
               REP     STOSB
DECIMAL_DONE:  RET

;----------------------------------------------;
; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.

ADD_IT:        PUSHF
               PUSH    SI
               STD                             ;Add or subtract right to left.
               MOV     SAVE_SIGN,0             ;Assume sign positive.

               MOV     DX,BX                   ;Save operand 2 pointer.
               MOV     CX,SIZE_MANTISSA        ;Point to end of operands.
               ADD     DI,CX
               ADD     BX,CX
               MOV     SI,BX

               CMP     AL,AH                   ;If both plus or minus, then
               JZ      ADD_SIGN                ; go ahead and add, else subtract
               CMP     AL,"-"                  ;If first operand
               JZ      SUB_IT                  ; negative and second positive
               XCHG    DI,SI                   ; swap operands before subtract.
               MOV     OPERAND,DX

SUB_IT:        MOV     BX,DI                   ;Save destination operand.
               CLC                             ;Start with no carry.
NEXT_SUB:      LODSB                           ;Get operand1
               SBB     AL,[DI]                 ;Subtract operand2 and the carry.
               AAS                             ;ASCII adjust after subtraction.
               STOSB                           ;Store it.
               LOOP    NEXT_SUB                ;Subtract next byte.
               MOV     AL,0                    ;If no carry, then positive.
               JNC     STORE_SIGN

               MOV     DI,BX                   ;Else negative so negate by
               CLC                             ; subtracting result from zero.
               MOV     CX,SIZE_MANTISSA
NEXT_NEGATE:   MOV     AL,CH                   ;Zero.
               SBB     AL,[DI]                 ;Subtract the byte.
               AAS                             ;ASCII adjust after subtraction.
               STOSB                           ;Store it.
               LOOP    NEXT_NEGATE
               MOV     AL,"-"                  ;Store negative sign.
               JMP     SHORT STORE_SIGN

;              Add procedure.
;----------------------------------------------;
ADD_SIGN:      MOV     SAVE_SIGN,AL            ;Either 2 +'s or 2 -'s; save it.
               CLC                             ;Start with no carry.
NEXT_ADD:      LODSB
               ADC     AL,[DI]                 ;Add operand two and carry.
               AAA                             ;ASCII adjust.
               STOSB
               LOOP    NEXT_ADD
               JC      ADD_ERROR               ;If carry out of number, overflow
               MOV     AL,SAVE_SIGN            ;Else, store sign.

STORE_SIGN:    STOSB
               POP     SI
               POPF
               RET

ADD_ERROR:     MOV     DX,OFFSET OVERFLOW
               POP     SI
               JMP     ERROR_EXIT

;----------------------------------------------;
; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.

MULTIPLY:      PUSH    BP
               PUSHF
               PUSH    SI
               XOR     DL,DL                   ;Assume plus result.
               CMP     AL,AH                   ;Signs the same?
               JZ      MULT_SIGN               ;If yes, assumed right.
               MOV     DL,"-"                  ;Else, negative.
MULT_SIGN:     MOV     SAVE_SIGN,DL

               STD                             ;Right to left.
               ADD     DI,SIZE_MANTISSA        ;Point to end of numbers.
               ADD     BX,SIZE_MANTISSA
               MOV     BP,DI                   ;Save destination.

               MOV     DI,OFFSET WORKSPACE_END
               MOV     DX,DI
               XOR     AX,AX                   ;Zero out scratch pad area.
               MOV     CX,SIZE WORKSPACE
               REP     STOSB

               MOV     DI,DX                   ;Point to workspace.
               MOV     CX,SIZE_MANTISSA        ;Number of digits to multiply.

NEXT_MULT:     PUSH    DI                      ;Save current product place.
               MOV     AL,[BX]                 ;Current multiplier digit zero?
               OR      AL,AL
               JZ      LOOP_MULT               ;If yes, no need to multiply.

               PUSH    CX                      ;Else, save multiplier counter.
               MOV     CX,SIZE_MANTISSA        ;Size of multiplicand size.
               MOV     DL,AL                   ;Multiplier.
               MOV     SI,BP                   ;Multiplicand pointer.
               XOR     DH,DH                   ;No carry yet.

NEXT_MULT2:    LODSB
               MUL     DL                      ;Multiply them.
               AAM                             ;Adjust BCD multiplication.
               ADD     AL,[DI]                 ;Accumulate as we go.
               AAA                             ;ASCII adjust.
               ADD     AL,DH                   ;Add the carry.
               AAA                             ;ASCII adjust.
               STOSB                           ;And store.
               MOV     DH,AH                   ;Save the carry.
               LOOP    NEXT_MULT2
               MOV     [DI],AH                 ;Store the carry.
               POP     CX                      ;Multiply by all digits.

LOOP_MULT:     POP     DI
               DEC     BX                      ;Next multiplier.
               DEC     DI                      ;Next product.
               LOOP    NEXT_MULT

               MOV     DI,OFFSET WORKSPACE_END - SIZE DECIMAL + 1
               MOV     CX,SIZE_MANTISSA
               CALL    ROUND                   ;Round up product.

               MOV     SI,OFFSET WORKSPACE_END - SIZE DECIMAL
               MOV     DI,BP                   ;Place decimal in product
               MOV     CX,SIZE_MANTISSA        ; by storing in destination.
               REP     MOVSB
               MOV     AL,SAVE_SIGN            ;Add the sign.
               STOSB

               MOV     CX,SIZE INTEGER         ;Check for overflow.
NEXT_OVER:     LODSB
               OR      AL,AL
               JNZ     MULT_ERROR
               LOOP    NEXT_OVER

               POP     SI
               POPF
               POP     BP
               RET

MULT_ERROR:    MOV     DX,OFFSET OVERFLOW
               POP     SI
               JMP     ERROR_EXIT


;----------------------------------------------;
; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.

DIVIDE:        PUSH    BP
               PUSHF
               PUSH    SI
               XOR     DL,DL                   ;Assume plus result.
               CMP     AL,AH                   ;Signs the same?
               JZ      DIV_SIGN                ;Is yes, assumed right.
               MOV     DL,"-"                  ;Else, negative.
DIV_SIGN:      MOV     SAVE_SIGN,DL

               MOV     BP,DI                   ;BP -> divisor.
               INC     BP                      ;Bump past sign.
               MOV     DI,OFFSET WORKSPACE     ;Quotient scratch pad.
               XOR     AL,AL
               MOV     CX,SIZE_DIVIDEND
               REP     STOSB                   ;Zero out quotient to start.

               MOV     DI,OFFSET DIVIDEND - 1  ;Move dividend to scratch pad.
               STOSB                           ;Zero in overflow byte.
               MOV     SI,BX                   ;Dividend.
               INC     SI                      ;Bump past sign.
               MOV     CX,SIZE_MANTISSA
               REP     MOVSB
               MOV     CX,SIZE_DIVIDEND - SIZE_MANTISSA
               REP     STOSB                   ;Zero out dividend underflow.

               MOV     DI,BP                   ;Look for zero divisor.
               MOV     CX,SIZE_MANTISSA
               REPZ    SCASB
               JNZ     DIVISOR_SIZE
               MOV     DX,OFFSET ZERO_DIVIDE   ;Can't divide by zero.
               POP     SI                      ;Command line pointer.
               JMP     ERROR_EXIT

DIVISOR_SIZE:  MOV     DI,OFFSET WORKSPACE     ;Quotient.
               ADD     DI,CX                   ;Point to first quotient storage.
               ADD     BP,SIZE_MANTISSA - 1
               SUB     BP,CX                   ;First non-zero divisor digit.
               INC     CX                      ;DH = subtraction counter.
               MOV     DX,CX                   ;DL = significant divisor digits.
               MOV     SI,OFFSET DIVIDEND
               MOV     CX,SIZE_DIVIDEND + 1
               SUB     CX,DX                   ;Number of times to divide.

NEXT_DIVIDE:   CMP     MODULO_FLAG,1           ;Is this a modulo division?
               JNZ     NO_MODULO
               CMP     DI,OFFSET WORKSPACE + SIZE_MANTISSA
               JAE     DIVIDE_END              ;If yes, divide integer only.

NO_MODULO:     PUSH    CX                      ;Division count.
               PUSH    DI                      ;Quotient.
               MOV     BX,SI                   ;Save dividend pointer.
               XOR     DH,DH                   ;Initialize sub counter to zero.

NEXT_DIVIDE2:  CMP     BYTE PTR [SI - 1],0     ;Overflow in remainder?
               JNZ     SUB_DIVISOR             ;If yes, divisor fits.
               MOV     CL,DL                   ;Divisor length.
               XOR     CH,CH                   ;Zero high half.
               MOV     DI,BP                   ;Divisor.
NEXT_DIV:      CMPSB                           ;Does it fit in remainder?
               JA      SUB_DIVISOR             ;If yes, subtract.
               JB      DIV_TOO_BIG             ;If no, save subtraction count.
               LOOP    NEXT_DIV

SUB_DIVISOR:   INC     DH                      ;Sub counter.
               MOV     SI,BP                   ;Divisor position.
               MOV     DI,BX                   ;Dividend position.
               MOV     CL,DL                   ;Significant divisor digits.
               XOR     CH,CH                   ;Zero in high half.
               DEC     CX                      ;Adjust for a moment.
               ADD     SI,CX                   ;Point to current position.
               ADD     DI,CX
               INC     CX

               STD                             ;Subtract right to left.
               CLC                             ;Start with no carry.
NEXT_DIV_SUB:  LODSB
               MOV     AH,AL                   ;Divisor digit.
               MOV     AL,[DI]                 ;Remainder digit.
               SBB     AL,AH                   ;Subtract.
               AAS                             ;ASCII adjust.  (BCD)
               STOSB                           ;Store in dividend.
               LOOP    NEXT_DIV_SUB
               SBB     BYTE PTR [DI],0         ;Subtract the carry, if any.

               CLD                             ;Back to forward.
               MOV     SI,BX
               JMP     NEXT_DIVIDE2            ;Next divisor.

DIV_TOO_BIG:   MOV     SI,BX                   ;Next dividend.
               POP     DI
               POP     CX
               MOV     AL,DH                   ;Store quotient.
               STOSB
               INC     SI
               LOOP    NEXT_DIVIDE

               MOV     DI,OFFSET WORKSPACE + SIZE_DIVIDEND - 1
               MOV     CX,SIZE_MANTISSA
               STD
               CALL    ROUND                   ;Round quotient.
               JC      DIVIDE_ERROR

               MOV     CX,SIZE_MANTISSA
               MOV     SI,OFFSET WORKSPACE + SIZE_DIVIDEND - 2
               MOV     DI,OPERAND              ;Store quotient in operand.
               ADD     DI,CX
               REP     MOVSB
               MOV     AL,SAVE_SIGN            ;Add the sign.
               STOSB

               MOV     CX,SIZE DECIMAL         ;Check for overflow.
NEXT_OVER2:    LODSB
               OR      AL,AL
               JNZ     DIVIDE_ERROR
               LOOP    NEXT_OVER2

DIVIDE_END:    POP     SI
               POPF
               POP     BP
               RET

DIVIDE_ERROR:  MOV     DX,OFFSET OVERFLOW
               POP     SI
               JMP     ERROR_EXIT

;----------------------------------------------;
; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
; OUTPUT: Result stored in DI.

MODULO:        PUSH    AX                      ;Save sign of dividend.
               PUSH    DI                      ;Divisor pointer.
               ADD     DI,SIZE INTEGER + 1
               MOV     CX,SIZE INTEGER
               STD
               CALL    ROUND                   ;Round divisor.
               JC      MODULO_ERROR
               POP     DI
               PUSH    DI
               CALL    MAKE_INTEGER            ;Truncate to integer.

               MOV     DI,BX
               ADD     DI,SIZE INTEGER + 1
               MOV     CX,SIZE INTEGER
               CALL    ROUND                   ;Round dividend.
               JC      MODULO_ERROR
               MOV     DI,BX
               CALL    MAKE_INTEGER            ;Truncate to integer.

               POP     DI
               MOV     MODULO_FLAG,1           ;Do modulo division.
               CLD
               CALL    DIVIDE
               MOV     MODULO_FLAG,0
               MOV     DI,OPERAND
               POP     AX                      ;Sign of dividend.
               MOV     AL,AH
               STOSB                           ;Store it.
               PUSH    SI
               MOV     SI,OFFSET DIVIDEND      ;Dividend has remainder.
               MOV     CX,SIZE_MANTISSA
               REP     MOVSB                   ;Store it in operand.
               POP     SI
               RET

MODULO_ERROR:  MOV     DX,OFFSET OVERFLOW
               JMP     ERROR_EXIT

;----------------------------------------------;
; INPUT: DI -> Start of mantissa.

MAKE_INTEGER:  PUSHF
               CLD
               ADD     DI,SIZE INTEGER + 1
               MOV     CX,SIZE DECIMAL
               XOR     AL,AL
               REP     STOSB
               POPF
               RET

;----------------------------------------------;
; INPUT:  DI -> End of mantissa + 1; Direction flag reversed.
;         CX = number of bytes to round.
; OUTPUT: CY=1 if overflow.

ROUND:         CMP     BYTE PTR [DI],5         ; 5 or greater?
               JB      ROUND_DONE              ;If no, done here.
               DEC     DI                      ;Else, move left one digit.
               STC                             ;Start with carry.
NEXT_ROUND:    MOV     AL,[DI]
               ADC     AL,0                    ;Add carry.
               AAA                             ;ASCII adjust.
               STOSB
               JNC     ROUND_END               ;If no carry, done.
               LOOP    NEXT_ROUND
               JMP     SHORT ROUND_END
ROUND_DONE:    CLC                             ;Return overflow.
ROUND_END:     RET

;----------------------------------------------;
RESULT:        MOV     DX,OFFSET RESULT_MSG    ;Display "Result is ".
               CALL    PRINT_STRING
               MOV     SI,OPERAND              ;Point to result.
               LODSB
               OR      AL,AL                   ;Print sign if negative.
               JZ      PRINT_INTEGER
               CALL    PRINT_CHAR

PRINT_INTEGER: XOR     DH,DH                   ;Non-zero digit flag.
               MOV     BL,3                    ;Comma every third digit.
               MOV     CX,INTEGER_PRECISION
               JMP     SHORT NEXT_INT2         ;Skip prefacing comma.

NEXT_INT:      OR      DH,DH                   ;Non-zero digit yet?
               JZ      NEXT_INT2               ;If no, skip.
               MOV     AX,CX                   ;Is position MOD 3 = 0?
               DIV     BL
               OR      AH,AH
               JNZ     NEXT_INT2               ;If no, no comma.
               MOV     AL,","                  ;Else, delimiting comma.
               CALL    PRINT_CHAR
 
NEXT_INT2:     LODSB
               OR      AL,AL                   ;Is digit zero?
               JNZ     PRINT_INT               ;If no, print it.
               OR      DH,DH                   ;Else, non-zero digit yet?
               JZ      LOOP_INTEGER            ;If no, skip.
PRINT_INT:     OR      DH,AL
               CALL    PRINT_NUMBER
LOOP_INTEGER:  LOOP    NEXT_INT

               PUSH    SI                      ;Save pointer.
               MOV     CX,DECIMAL_PRECISION    ;Count the significant decimal
               MOV     BX,CX                   ; places.
               INC     BX
NEXT_FRACTION: LODSB
               OR      AL,AL
               JZ      LOOP_FRACTION
               MOV     BX,CX                   ;If non-zero, save count.
LOOP_FRACTION: LOOP    NEXT_FRACTION
               POP     SI                      ;Restore pointer.
               NEG     BX                      ;Get count of significant.
               ADD     BX,DECIMAL_PRECISION + 1
               MOV     CX,DECIMAL_PLACES       ;Use the larger of significant
               CMP     CX,BX                   ; digits; number entered or
               JAE     DO_FRACTION             ; result.
               MOV     CX,BX

DO_FRACTION:   JCXZ    CK_NULL                 ;If no significant, done here.
               OR      DH,DH                   ;Else, was there a non-zero
               JNZ     PRINT_DECIMAL           ; integer?
               XOR     AL,AL                   ;If no, preface with zero.
               CALL    PRINT_NUMBER
PRINT_DECIMAL: MOV     AL,"."                  ;Display delimiting decimal.
               CALL    PRINT_CHAR

               XOR     BL,BL                   ;Comma counter.
NEXT_DEC:      CMP     BL,3
               JNZ     NEXT_DEC2
               MOV     AL,","
               CALL    PRINT_CHAR
               XOR     BL,BL
NEXT_DEC2:     LODSB                           ;Display decimal digits.
               CALL    PRINT_NUMBER
               INC     BL
LOOP_DEC:      LOOP    NEXT_DEC
               JMP     SHORT RESULT_END

CK_NULL:       OR      DH,DH                   ;Was there a non-zero integer?
               JNZ     RESULT_END              ;If yes, done here.
               XOR     AL,AL                   ;Else, display solitary, zero.
               CALL    PRINT_NUMBER
RESULT_END:    MOV     DX,OFFSET CRLF          ;New line.
               CALL    PRINT_STRING

;----------------------------------------------;
SAVE_ANSWER:   MOV     SI,OPERAND              ;Copy operand to answer.
               MOV     DI,OFFSET ANSWER
               MOV     CX,SIZE NUMBER
               REP     MOVSB

               MOV     AH,30H                  ;Get DOS version.
               INT     21H
               CMP     AL,3                    ;Filename in environment
               JB      TRY_CURRENT             ; not support before DOS 3.x.

               MOV     DS,DS:[2CH]             ;Segment of environment.
               XOR     SI,SI
FIND_ENV_END:  LODSB                           ;Find end of environment.
               OR      AL,AL
               JNZ     FIND_ENV_END
               LODSB
               OR      AL,AL
               JNZ     FIND_ENV_END

               INC     SI                      ;Bump pointer past word count.
               INC     SI
               MOV     DI,OFFSET WORKSPACE     ;Copy our name.
               CALL    COPY_NAME
               PUSH    CS
               POP     DS
               MOV     DX,OFFSET WORKSPACE     ;Try to write answer.
               CALL    WRITE_ANS
               JNC     ANSWER_END

TRY_CURRENT:   MOV     DX,OFFSET COMPUTE       ;Try current directory.
               CALL    WRITE_ANS
               JNC     ANSWER_END

; See if PATH= is in environment.
               MOV     DS,DS:[2CH]             ;Segment of environment.
LOOK_AT_PATH:  MOV     BX,-1                   ;Offset zero in environment.
NEXT_PATH:     INC     BX
               MOV     SI,BX
               CMP     BYTE PTR [SI],0         ;Terminating null?
               JNZ     PATH_SEARCH
               CMP     BYTE PTR [SI+1],0       ;Double null?
               JNZ     NEXT_PATH
               JMP     SHORT CANT_SAVE         ;If so, end of environment.

PATH_SEARCH:   MOV     DI,OFFSET PATH          ;Search for "PATH=".
               MOV     CX,PATH_LEN
               REP     CMPSB
               JNZ     NEXT_PATH

; PATH= found; search for paths.
NEXT_STRING:   CMP     BYTE PTR [SI],0         ;Terminating null?
               JZ      CANT_SAVE
               CMP     BYTE PTR [SI-1],0       ;Double null?
               JZ      CANT_SAVE

MOVE_STRING:   MOV     DI,OFFSET WORKSPACE     ;Get path.
NEXT_BYTE:     LODSB
               OR      AL,AL
               JZ      SEARCH_PATH
               CMP     AL,";"
               JZ      SEARCH_PATH
               STOSB
               JMP     NEXT_BYTE

SEARCH_PATH:   CALL    COPY_PATH               ;Create filespec.
               PUSH    DS
               PUSH    CS
               POP     DS
               MOV     DX,OFFSET WORKSPACE
               CALL    WRITE_ANS               ;Try to write answer.
               POP     DS
               JC      NEXT_STRING             ;If failed, try next path.
               JMP     SHORT ANSWER_END        ;Else, done.

CANT_SAVE:     PUSH    CS
               POP     DS
               MOV     DX,OFFSET NOT_FOUND     ;Answer write error message.
               CALL    PRINT_STRING

ANSWER_END:    RET

;----------------------------------------------;
COPY_NAME:     LODSB
               STOSB
               OR      AL,AL
               JNZ     COPY_NAME
               RET

;----------------------------------------------;
COPY_PATH:     PUSH    SI
               PUSH    DS
               CMP     DI,OFFSET WORKSPACE
               JZ      MOVE_NAME
               CMP     BYTE PTR ES:[DI-1],"\"  ;Filespec delimiter?
               JZ      MOVE_NAME
               CMP     BYTE PTR ES:[DI-1],":"
               JZ      MOVE_NAME
               MOV     AL,"\"                  ;If no, add one.
               STOSB

MOVE_NAME:     PUSH    CS
               POP     DS
               MOV     SI,OFFSET COMPUTE       ;Tack on "COMPUTE.COM".
               MOV     CX,COMPUTE_LEN
               REP     MOVSB

COPY_END:      POP     DS
               POP     SI
               RET

;----------------------------------------------;
; INPUT:  DX -> Compute.com filespec.
; OUTPUT: CY=0 if successful; CY=1 if failed.

WRITE_ANS:     PUSH    SI
               MOV     AX,3D02H                ;Open for reading and writing.
               INT     21H
               JC      WRITE_END
               MOV     BX,AX
               MOV     DX,OFFSET WORKSPACE
               MOV     CX,SIZE WORKSPACE
               MOV     AH,3FH                  ;Read signature.
               INT     21H
               JC      WRITE_END

               MOV     SI,100H                 ;Make sure we found correct
               MOV     DI,OFFSET WORKSPACE     ; Compute.com.
               REP     CMPSB
               STC
               JNZ     WRITE_END

               XOR     CX,CX
               MOV     DX,OFFSET ANSWER - 100H
               MOV     AX,4200H                ;Seek offset of answer.
               INT     21H
               JC      WRITE_END

               MOV     DX,OFFSET ANSWER
               MOV     CX,SIZE ANSWER
               MOV     AH,40H                  ;Write answer to disk.
               INT     21H
               JC      WRITE_END

               MOV     AH,3EH                  ;Close file.
               INT     21H
WRITE_END:     POP     SI
               RET

;----------------------------------------------;
PRINT_NUMBER:  ADD     AL,"0"                  ;Binary to ASCII.
PRINT_CHAR:    MOV     DL,AL
               MOV     AH,2                    ;Print char via DOS.
               INT     21H
               RET

;----------------------------------------------;
PRINT_STRING:  MOV     AH,9                    ;Print string via DOS.
               INT     21H
               RET

;----------------------------------------------;
; HEAP

INITIAL_VALUE  NUMBER  <>

_TEXT          ENDS
               END     START
