home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / pcmagazi / 1990 / 10 / compute.asm next >
Assembly Source File  |  1990-01-27  |  44KB  |  1,017 lines

  1. ;--------------------------------------------------------------------;
  2. ;  COMPUTE * PC Magazine * Michael J. Mefford                        ;
  3. ;  Command line five function math calculator.                       ;
  4. ;--------------------------------------------------------------------;
  5.  
  6. _TEXT          SEGMENT PUBLIC 'CODE'
  7.                ASSUME  CS:_TEXT,DS:_TEXT,ES:_TEXT,SS:_TEXT
  8.                ORG     100H
  9. START:         JMP     MAIN
  10.  
  11.  
  12. ;              DATA AREA
  13. ;              ---------
  14. SIGNATURE      DB      CR,SPACE,SPACE,SPACE,CR,LF
  15.  
  16. COPYRIGHT      DB      "COMPUTE 1.0 (C) 1990 Ziff Communications Co.",CR,LF
  17. PROGRAMMER     DB      "PC Magazine ",BOX," Michael J. Mefford",CR,LF,LF,"$"
  18. SYNTAX         DB      "Syntax: COMPUTE arithmetic expression",CR,LF
  19.                DB      "Operators supported are + - * / % ( ) [ ] 0-9 x",CR,LF
  20.                DB      "Note: % = modulo, remainder of integer division.",CR,LF
  21.                DB      "      x = answer of last compute."
  22. CRLFLF         DB      LF
  23. CRLF           DB      CR,LF
  24.                DB      "$",CTRL_Z
  25.  
  26. CR             EQU     13
  27. LF             EQU     10
  28. CTRL_Z         EQU     26
  29. SPACE          EQU     32
  30. BOX            EQU     254
  31. COMMA          EQU     ","
  32.  
  33. INTEGER_PRECISION      =    20
  34. DECIMAL_PRECISION      =    20
  35. PRECISION      =       INTEGER_PRECISION + DECIMAL_PRECISION
  36.  
  37. NUMBER         STRUC
  38. SIGN           DB      0
  39. INTEGER        DB      INTEGER_PRECISION DUP (0)
  40. DECIMAL        DB      DECIMAL_PRECISION DUP (0)
  41. NUMBER         ENDS
  42.  
  43. SIZE_MANTISSA  =       SIZE INTEGER + SIZE DECIMAL
  44. SIZE_DIVIDEND  =       SIZE_MANTISSA + SIZE DECIMAL + 1
  45.  
  46. OPERAND        DW      INITIAL_VALUE
  47. OPERATORS      DB      "+-"
  48. UNARY_PREFIXES LABEL   BYTE
  49. PRECEDENCE     DB      "*/%"
  50. PRECEDENCE_CNT =       $ - PRECEDENCE
  51. OPERATOR_CNT   =       $ - OPERATORS
  52.                DB      "([",0
  53. PREFIXES_CNT   =       $ - UNARY_PREFIXES
  54.  
  55. LAST_CHAR      DB      ?
  56.  
  57. PAREN_CNT      DW      0
  58. PAREN_TYPE     DB      50 DUP (0)
  59. WORKSPACE      DB      2 * SIZE_MANTISSA DUP (?)
  60. WORKSPACE_END  EQU     $ - 1
  61.                DB      ?                       ;Dividend overflow.
  62. DIVIDEND       DB      SIZE_DIVIDEND DUP (?)
  63. ANSWER         DB      SIZE NUMBER DUP (0)
  64.  
  65. DECIMAL_PLACES DW      0                       ;Significant decimal digits.
  66. SAVE_SIGN      DB      ?
  67. MODULO_FLAG    DB      0                       ; =1 if modulo division.
  68.  
  69. COMPUTE        DB      "COMPUTE.COM",0
  70. COMPUTE_LEN    EQU     $ - COMPUTE
  71. PATH           DB      "PATH="
  72. PATH_LEN       EQU     $ - PATH
  73. NOT_FOUND      DB      CR,LF,"Could not save answer in Compute.com",CR,LF
  74.                DB      "Compute.com must be in current directory",CR,LF
  75.                DB      "or in DOS PATH= directory.",CR,LF,"$"
  76. RESULT_MSG     DB      "Result is $"
  77. UNRECOGNIZED   DB      "Unrecognized character$"
  78. MISSING_PAREN  DB      "Missing parenthesis or bracket$"
  79. UNMATCHED      DB      "Miss-matched parenthesis with bracket$"
  80. ZERO_DIVIDE    DB      "Divide by zero$"
  81. OVERFLOW       DB      "Overflow$"
  82. LOGICAL        DB      "Logic error$"
  83. MEMORY         DB      "Requires 10K of memory$"
  84. ERROR1         DB      "Error ^",CR,LF,"$"
  85. ERROR2         DB      "^ Error$",CR,LF,"$"
  86.  
  87.  
  88. ;              CODE AREA
  89. ;              ---------
  90. MAIN           PROC    NEAR
  91.                CLD                             ;Strings forward.
  92.  
  93.                MOV     DX,OFFSET SIGNATURE     ;Announce ourselves.
  94.                CALL    PRINT_STRING
  95.  
  96.                MOV     BX,10 / 16 * 1024       ;Minimum of 10K required.
  97.                MOV     AH,4AH                  ;Request via DOS.
  98.                INT     21H
  99.                MOV     DX,OFFSET MEMORY
  100.                JC      ERROR_EXIT              ;Exit with message if not enough.
  101.  
  102.                MOV     SP,16 * 1024 - 2        ;Else, setup stack at end of seg.
  103.  
  104.                MOV     SI,81H                  ;Point to command line.
  105.                XOR     AX,AX                   ;No characters yet.
  106.                XOR     DX,DX                   ;Not unary.
  107.                CALL    PARSE                   ;Parse command line.
  108.                CALL    PARSE_DELIMIT           ;See if reached end.
  109.                MOV     DX,OFFSET LOGICAL       ;If not, logical error.
  110.                JNC     ERROR_EXIT
  111.  
  112.                CMP     OPERAND,OFFSET INITIAL_VALUE
  113.                JNZ     CK_PARENS
  114.                MOV     DX,OFFSET SYNTAX        ;Did we get a value.
  115.                CALL    PRINT_STRING            ;If not, print syntax and exit.
  116.                JMP     SHORT ERROR_END
  117.  
  118. CK_PARENS:     MOV     DX,OFFSET MISSING_PAREN
  119.                CMP     PAREN_CNT,0             ;Did we match all parenthesis
  120.                JNZ     ERROR_EXIT              ; and brackets?
  121.  
  122.                CALL    RESULT                  ;Every thing seems OK so
  123.                                                ; display result.
  124. EXIT:          MOV     AX,4C00H                ;Exit with ErrorLevel=0
  125.                INT     21H
  126. MAIN           ENDP
  127.  
  128. ;----------------------------------------------;
  129. ERROR_EXIT:    PUSH    DX                      ;Save error message.
  130.                MOV     DX,OFFSET SYNTAX        ;Display syntax.
  131.                CALL    PRINT_STRING
  132.                POP     DX
  133.                CALL    PRINT_STRING            ;Display error.
  134.                MOV     DX,OFFSET CRLFLF
  135.                CALL    PRINT_STRING
  136.                MOV     BX,SI                   ;Current command line pointer.
  137.                MOV     SI,81H                  ;Point at start of command line.
  138.                CLD                             ;Make strings are forward.
  139.  
  140. NEXT_LINE:     MOV     CX,79                   ;1 less than 80 char. display.
  141. NEXT_ERROR:    LODSB
  142.                CMP     AL,CR                   ;End of command line?
  143.                JZ      PRINT_POINTER
  144.                CALL    PRINT_CHAR              ;If no, print character.
  145.                CMP     SI,BX                   ;Pointing to error position?
  146.                JZ      PRINT_POINTER           ;If yes, done here.
  147.                LOOP    NEXT_ERROR              ;Else, continue.
  148.                MOV     DX,OFFSET CRLF          ;Filled on line; new line.
  149.                CALL    PRINT_STRING
  150.                JMP     NEXT_LINE
  151.  
  152. PRINT_POINTER: MOV     DX,OFFSET CRLF          ;New line.
  153.                CALL    PRINT_STRING
  154.                SUB     CX,79                   ;Calculate chars. on line above.
  155.                NEG     CX
  156.                MOV     BX,OFFSET ERROR1        ;Assume left side of display.
  157.                CMP     CX,40
  158.                JB      LEFT_ARROW
  159.                SUB     CX,6
  160.                JMP     SHORT NEXT_SPACE
  161. LEFT_ARROW:    MOV     BX,OFFSET ERROR2
  162.  
  163. NEXT_SPACE:    MOV     AL,SPACE                ;Pad with spaces over to error.
  164.                CALL    PRINT_CHAR
  165.                LOOP    NEXT_SPACE
  166.                MOV     DX,BX                   ;Display "Error ^".
  167.                CALL    PRINT_STRING
  168.  
  169. ERROR_END:     MOV     AX,4C01H                ;Exit with ErrorLevel = 1.
  170.                INT     21H
  171.  
  172. ;----------------------------------------------;
  173. ; Recursive parsing routine.                   ;
  174. ;----------------------------------------------;
  175. PARSE:         PUSH    BP                      ;Preserve stack index.
  176.                PUSH    DX                      ;Unary flag.
  177.                PUSH    AX                      ;Operator.
  178.                MOV     BP,SP                   ;Point to local vars.
  179. NEXT_PARSE:    CALL    PARSE_DELIMIT           ;Parse white space.
  180.                JNC     GET_CHAR                ;If command line end, exit.
  181.                JMP     PARSE_END
  182.  
  183. GET_CHAR:      LODSB                           ;Else, get the character.
  184.                MOV     DI,OFFSET OPERATORS     ;Is it a function operator?
  185.                MOV     CX,OPERATOR_CNT
  186.                REPNZ   SCASB
  187.                JNZ     CK_NUM                  ;If no, check if number.
  188.  
  189.                XOR     DL,DL                   ;Assume not unary.
  190.                CMP     AL,"+"                  ;Is it plus or minus?
  191.                JZ      CK_UNARY
  192.                CMP     AL,"-"
  193.                JNZ     GET_OPERAND2            ;If no, not unary.
  194. CK_UNARY:      PUSH    AX
  195.                MOV     AL,LAST_CHAR            ;Else, does previous char
  196.                MOV     DI,OFFSET UNARY_PREFIXES  ; suggest unary?
  197.                MOV     CX,PREFIXES_CNT
  198.                REPNZ   SCASB
  199.                POP     AX
  200.                JNZ     GET_OPERAND2
  201.                INC     DL                      ;If yes, it's unary.
  202.  
  203. GET_OPERAND2:  PUSH    WORD PTR LAST_CHAR      ;Save last char.
  204.                MOV     LAST_CHAR,AL            ;Store current char.
  205.                PUSH    OPERAND                 ;Save operand pointer.
  206.                CALL    PARSE                   ;Get second operand.
  207.                POP     BX                      ;First operand.
  208.                POP     CX                      ;Last character.
  209.                MOV     DH,AL                   ;Save operator.
  210.                MOV     DI,OPERAND              ;Second operand.
  211.                MOV     AL,[DI]                 ;Get signs.
  212.                MOV     AH,[BX]
  213.                CMP     DH,"*"                  ;Check which operator.
  214.                JZ      DO_MULT
  215.                CMP     DH,"/"
  216.                JZ      DO_DIV
  217.                CMP     DH,"%"
  218.                JZ      DO_MOD
  219.                                                ;Must be plus or minus.
  220.                OR      DL,DL                   ;Was it unary.
  221.                JZ      DO_ADD                  ;If no, add operands.
  222.                MOV     AL,CL                   ;Else, was last character
  223.                CALL    CK_PRECEDENCE           ; "*", "/" or "%" ?
  224.                JNZ     NEXT_PARSE              ;If no, just continue parsing.
  225.                JMP     PARSE_END               ;Else, return second operand.
  226.  
  227. DO_ADD:        CALL    ADD_IT                  ;Add operands.
  228.                MOV     AL,LAST_CHAR
  229.                CMP     AL,")"                  ;Is this an equantity?
  230.                JZ      PARSE_DONE              ;Is yes, return it.
  231.                CMP     AL,"]"
  232.                JZ      PARSE_DONE
  233.                JMP     NEXT_PARSE              ;Else, continue parsing.
  234.  
  235. DO_MULT:       CALL    MULTIPLY
  236.                JMP     NEXT_PARSE
  237.  
  238. DO_DIV:        CALL    DIVIDE
  239.                JMP     NEXT_PARSE
  240.  
  241. DO_MOD:        CALL    MODULO
  242.                JMP     NEXT_PARSE
  243.  
  244. PARSE_DONE:    JMP     PARSE_END               ;Lilly pad for return.
  245.  
  246. CK_NUM:        MOV     LAST_CHAR,AL            ;Store current character.
  247.                CMP     AL,"X"                  ;Is it "answer" character?
  248.                JZ      DO_ANS
  249.                CMP     AL,"x"
  250.                JNZ     CK_DIGIT
  251. DO_ANS:        PUSH    SI
  252.                MOV     SI,OFFSET ANSWER        ;If yes copy answer to operand.
  253.                ADD     OPERAND,SIZE NUMBER     ;Point to next operand storage.
  254.                MOV     DI,OPERAND
  255.                PUSH    DI
  256.                MOV     CX,SIZE NUMBER
  257.                REP     MOVSB
  258.                POP     DI
  259.                MOV     AL,[BP]                 ;Get sign
  260.                CMP     AL,"-"
  261.                JZ      ANS_SIGN
  262.                XOR     AL,AL                   ; and store.
  263. ANS_SIGN:      XOR     [DI],AL
  264.                POP     SI
  265.                JMP     SHORT CK_UNARY_NUM      ;Check if return or not.
  266.  
  267. CK_DIGIT:      CMP     AL,"."                  ;Is char a num char?
  268.                JZ      DO_NUM
  269.                CMP     AL,"0"
  270.                JB      CK_PAREN
  271.                CMP     AL,"9"
  272.                JA      CK_PAREN
  273. DO_NUM:        DEC     SI                      ;If yes, point to it.
  274.                ADD     OPERAND,SIZE NUMBER     ;Point to next operand storage.
  275.                CALL    GET_NUMBER              ;Get the number.
  276. CK_UNARY_NUM:  CMP     BYTE PTR [BP + 2],1     ;Is this a unary number?
  277.                JZ      PARSE_END               ;If yes, return the number.
  278.                MOV     AL,[BP]                 ;Get operator.
  279.                CALL    CK_PRECEDENCE           ;If high precedence, go do it.
  280.                JZ      PARSE_END
  281.                JMP     NEXT_PARSE              ;Else, continue parsing.
  282.  
  283. CK_PAREN:      MOV     AH,")"
  284.                CMP     AL,"("                  ;Is it an open parenthesis
  285.                JZ      FOUND_PAREN             ; or bracket?
  286.                CMP     AL,AH
  287.                JZ      CK_OPEN
  288.                MOV     AH,"]"
  289.                CMP     AL,"["
  290.                JZ      FOUND_PAREN
  291.                CMP     AL,AH                   ;Was it a close parenthesis or
  292.                MOV     DX,OFFSET UNRECOGNIZED  ; or bracket?
  293.                JNZ     PARSE_ERROR             ;If no, must be invalid char.
  294.  
  295. CK_OPEN:       DEC     PAREN_CNT               ;If close, decrement level.
  296.                MOV     DX,OFFSET MISSING_PAREN ;If overflow then had no
  297.                JC      PARSE_ERROR             ; open.
  298.                MOV     BX,PAREN_CNT            ;Retrieve level index
  299.                MOV     PAREN_TYPE[BX],AH       ; and store close type.
  300.                JMP     SHORT PARSE_END         ;Return results.
  301.  
  302. FOUND_PAREN:   PUSH    PAREN_CNT               ;Save level index.
  303.                INC     PAREN_CNT               ;Next level.
  304.                XOR     DX,DX                   ;Not unary flag.
  305.                CALL    PARSE                   ;Parse the equantity.
  306.                POP     BX                      ;Retrieve level.
  307.                CMP     AH,PAREN_TYPE[BX]       ;Did we close with matching
  308.                MOV     DX,OFFSET UNMATCHED     ; parenthesis or bracket?
  309.                JNZ     PARSE_ERROR
  310.                MOV     AL,[BP]                 ;Retrieve, prefix operator.
  311.                CALL    CK_PRECEDENCE           ;Was it priority operator?
  312.                JZ      PARSE_END               ;If yes, return value.
  313.                CMP     AL,"-"                  ;Else store sign.
  314.                JNZ     PAREN_END
  315.                MOV     BX,OPERAND
  316.                XOR     [BX],AL
  317. PAREN_END:     JMP     NEXT_PARSE              ;And parse next.
  318.  
  319. PARSE_END:     POP     AX
  320.                POP     DX
  321.                POP     BP
  322.                RET
  323.  
  324. PARSE_ERROR:   JMP     ERROR_EXIT
  325.                RET
  326.  
  327. ;-------------------------------------------------------------------;
  328. ; INPUT: SI -> string                                               ;
  329. ; OUTPUT SI -> first non-white space and CY=0 or SI -> CR and CY=1. ;
  330. ;-------------------------------------------------------------------;
  331. PARSE_DELIMIT: LODSB                           ;Get a byte.
  332.                CMP     AL,CR
  333.                JZ      STRING_END
  334.                CMP     AL,SPACE                ;Is it a space char or below?
  335.                JBE     PARSE_DELIMIT
  336.                DEC     SI                      ;Else, adjust pointer to
  337.                CLC
  338.                RET                             ; string start.
  339.  
  340. STRING_END:    DEC     SI
  341.                STC
  342.                RET
  343.  
  344. ;----------------------------------------------;
  345. CK_PRECEDENCE: MOV     DI,OFFSET PRECEDENCE
  346.                MOV     CX,PRECEDENCE_CNT
  347.                REPNZ   SCASB
  348.                RET
  349.  
  350. ;----------------------------------------------;
  351. GET_NUMBER:    MOV     DI,OFFSET WORKSPACE     ;Store integer temporarily
  352.                MOV     CX,SIZE INTEGER + 1     ; so can store right justified.
  353. NEXT_INTEGER:  LODSB
  354.                CMP     AL,COMMA                ;Ignore commas.
  355.                JZ      NEXT_INTEGER
  356.                CMP     AL,"."                  ;Decimal point; end of integer.
  357.                JZ      STORE_INTEGER
  358.  
  359. GET_NUM2:      SUB     AL,"0"
  360.                JC      INTEGER_END
  361.                CMP     AL,9
  362.                JA      INTEGER_END
  363.                STOSB                           ;Store the number.
  364.                LOOP    NEXT_INTEGER
  365.                MOV     DX,OFFSET OVERFLOW      ;If got here, number too big.
  366.                JMP     ERROR_EXIT
  367.  
  368. INTEGER_END:   DEC     SI
  369. STORE_INTEGER: PUSH    SI                      ;Save command line pointer.
  370.                MOV     SI,DI
  371.                DEC     SI                      ;End of workspace.
  372.                PUSHF
  373.                STD
  374.                MOV     DI,OPERAND
  375.                ADD     DI,SIZE INTEGER         
  376.  
  377.                MOV     DX,CX
  378.                DEC     DX                      ;Zero pad count.
  379.  
  380.                NEG     CX
  381.                ADD     CX,SIZE INTEGER + 1
  382.                JCXZ    PAD_INTEGER
  383.                REP     MOVSB                   ;Store integer part in operand.
  384.  
  385. PAD_INTEGER:   MOV     CX,DX
  386.                JCXZ    INTEGER_DONE
  387.                XOR     AL,AL
  388.                REP     STOSB                   ;Pad balance of integer with 0.
  389. INTEGER_DONE:  MOV     AL,[BP]                 ;Get sign
  390.                CMP     AL,"-"
  391.                JZ      INT_SIGN
  392.                XOR     AL,AL                   ; and store.
  393. INT_SIGN:      STOSB
  394.                POPF
  395.  
  396.  
  397. DO_DECIMAL:    POP     SI                      ;Restore command line pointer.
  398.                MOV     DI,OPERAND
  399.                ADD     DI,SIZE SIGN + SIZE INTEGER   ;Decimal operand storage.
  400.                MOV     CX,SIZE DECIMAL + 1           ;Left justified.
  401.                XOR     DX,DX                   ;Decimal counter.
  402. NEXT_DECIMAL:  LODSB
  403.                SUB     AL,"0"
  404.                JC      DECIMAL_END
  405.                CMP     AL,9
  406.                JA      DECIMAL_END
  407.                STOSB
  408.                INC     DX                      ;Increment decimal count.
  409.                LOOP    NEXT_DECIMAL
  410.                MOV     DX,OFFSET OVERFLOW      ;If got here, number too big.
  411.                JMP     ERROR_EXIT
  412.  
  413. DECIMAL_END:   DEC     SI                      ;Adjust command line pointer.
  414.                CMP     DX,DECIMAL_PLACES       ;Store significant decimal
  415.                JBE     PAD_DECIMAL             ; places.
  416.                MOV     DECIMAL_PLACES,DX
  417.  
  418. PAD_DECIMAL:   DEC     CX                      ;Pad balance with zeros.
  419.                JCXZ    DECIMAL_DONE
  420.                XOR     AL,AL
  421.                REP     STOSB
  422. DECIMAL_DONE:  RET
  423.  
  424. ;----------------------------------------------;
  425. ; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
  426. ; OUTPUT: Result stored in DI.
  427.  
  428. ADD_IT:        PUSHF
  429.                PUSH    SI
  430.                STD                             ;Add or subtract right to left.
  431.                MOV     SAVE_SIGN,0             ;Assume sign positive.
  432.  
  433.                MOV     DX,BX                   ;Save operand 2 pointer.
  434.                MOV     CX,SIZE_MANTISSA        ;Point to end of operands.
  435.                ADD     DI,CX
  436.                ADD     BX,CX
  437.                MOV     SI,BX
  438.  
  439.                CMP     AL,AH                   ;If both plus or minus, then
  440.                JZ      ADD_SIGN                ; go ahead and add, else subtract
  441.                CMP     AL,"-"                  ;If first operand
  442.                JZ      SUB_IT                  ; negative and second positive
  443.                XCHG    DI,SI                   ; swap operands before subtract.
  444.                MOV     OPERAND,DX
  445.  
  446. SUB_IT:        MOV     BX,DI                   ;Save destination operand.
  447.                CLC                             ;Start with no carry.
  448. NEXT_SUB:      LODSB                           ;Get operand1
  449.                SBB     AL,[DI]                 ;Subtract operand2 and the carry.
  450.                AAS                             ;ASCII adjust after subtraction.
  451.                STOSB                           ;Store it.
  452.                LOOP    NEXT_SUB                ;Subtract next byte.
  453.                MOV     AL,0                    ;If no carry, then positive.
  454.                JNC     STORE_SIGN
  455.  
  456.                MOV     DI,BX                   ;Else negative so negate by
  457.                CLC                             ; subtracting result from zero.
  458.                MOV     CX,SIZE_MANTISSA
  459. NEXT_NEGATE:   MOV     AL,CH                   ;Zero.
  460.                SBB     AL,[DI]                 ;Subtract the byte.
  461.                AAS                             ;ASCII adjust after subtraction.
  462.                STOSB                           ;Store it.
  463.                LOOP    NEXT_NEGATE
  464.                MOV     AL,"-"                  ;Store negative sign.
  465.                JMP     SHORT STORE_SIGN
  466.  
  467. ;              Add procedure.
  468. ;----------------------------------------------;
  469. ADD_SIGN:      MOV     SAVE_SIGN,AL            ;Either 2 +'s or 2 -'s; save it.
  470.                CLC                             ;Start with no carry.
  471. NEXT_ADD:      LODSB
  472.                ADC     AL,[DI]                 ;Add operand two and carry.
  473.                AAA                             ;ASCII adjust.
  474.                STOSB
  475.                LOOP    NEXT_ADD
  476.                JC      ADD_ERROR               ;If carry out of number, overflow
  477.                MOV     AL,SAVE_SIGN            ;Else, store sign.
  478.  
  479. STORE_SIGN:    STOSB
  480.                POP     SI
  481.                POPF
  482.                RET
  483.  
  484. ADD_ERROR:     MOV     DX,OFFSET OVERFLOW
  485.                POP     SI
  486.                JMP     ERROR_EXIT
  487.  
  488. ;----------------------------------------------;
  489. ; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
  490. ; OUTPUT: Result stored in DI.
  491.  
  492. MULTIPLY:      PUSH    BP
  493.                PUSHF
  494.                PUSH    SI
  495.                XOR     DL,DL                   ;Assume plus result.
  496.                CMP     AL,AH                   ;Signs the same?
  497.                JZ      MULT_SIGN               ;If yes, assumed right.
  498.                MOV     DL,"-"                  ;Else, negative.
  499. MULT_SIGN:     MOV     SAVE_SIGN,DL
  500.  
  501.                STD                             ;Right to left.
  502.                ADD     DI,SIZE_MANTISSA        ;Point to end of numbers.
  503.                ADD     BX,SIZE_MANTISSA
  504.                MOV     BP,DI                   ;Save destination.
  505.  
  506.                MOV     DI,OFFSET WORKSPACE_END
  507.                MOV     DX,DI
  508.                XOR     AX,AX                   ;Zero out scratch pad area.
  509.                MOV     CX,SIZE WORKSPACE
  510.                REP     STOSB
  511.  
  512.                MOV     DI,DX                   ;Point to workspace.
  513.                MOV     CX,SIZE_MANTISSA        ;Number of digits to multiply.
  514.  
  515. NEXT_MULT:     PUSH    DI                      ;Save current product place.
  516.                MOV     AL,[BX]                 ;Current multiplier digit zero?
  517.                OR      AL,AL
  518.                JZ      LOOP_MULT               ;If yes, no need to multiply.
  519.  
  520.                PUSH    CX                      ;Else, save multiplier counter.
  521.                MOV     CX,SIZE_MANTISSA        ;Size of multiplicand size.
  522.                MOV     DL,AL                   ;Multiplier.
  523.                MOV     SI,BP                   ;Multiplicand pointer.
  524.                XOR     DH,DH                   ;No carry yet.
  525.  
  526. NEXT_MULT2:    LODSB
  527.                MUL     DL                      ;Multiply them.
  528.                AAM                             ;Adjust BCD multiplication.
  529.                ADD     AL,[DI]                 ;Accumulate as we go.
  530.                AAA                             ;ASCII adjust.
  531.                ADD     AL,DH                   ;Add the carry.
  532.                AAA                             ;ASCII adjust.
  533.                STOSB                           ;And store.
  534.                MOV     DH,AH                   ;Save the carry.
  535.                LOOP    NEXT_MULT2
  536.                MOV     [DI],AH                 ;Store the carry.
  537.                POP     CX                      ;Multiply by all digits.
  538.  
  539. LOOP_MULT:     POP     DI
  540.                DEC     BX                      ;Next multiplier.
  541.                DEC     DI                      ;Next product.
  542.                LOOP    NEXT_MULT
  543.  
  544.                MOV     DI,OFFSET WORKSPACE_END - SIZE DECIMAL + 1
  545.                MOV     CX,SIZE_MANTISSA
  546.                CALL    ROUND                   ;Round up product.
  547.  
  548.                MOV     SI,OFFSET WORKSPACE_END - SIZE DECIMAL
  549.                MOV     DI,BP                   ;Place decimal in product
  550.                MOV     CX,SIZE_MANTISSA        ; by storing in destination.
  551.                REP     MOVSB
  552.                MOV     AL,SAVE_SIGN            ;Add the sign.
  553.                STOSB
  554.  
  555.                MOV     CX,SIZE INTEGER         ;Check for overflow.
  556. NEXT_OVER:     LODSB
  557.                OR      AL,AL
  558.                JNZ     MULT_ERROR
  559.                LOOP    NEXT_OVER
  560.  
  561.                POP     SI
  562.                POPF
  563.                POP     BP
  564.                RET
  565.  
  566. MULT_ERROR:    MOV     DX,OFFSET OVERFLOW
  567.                POP     SI
  568.                JMP     ERROR_EXIT
  569.  
  570.  
  571. ;----------------------------------------------;
  572. ; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
  573. ; OUTPUT: Result stored in DI.
  574.  
  575. DIVIDE:        PUSH    BP
  576.                PUSHF
  577.                PUSH    SI
  578.                XOR     DL,DL                   ;Assume plus result.
  579.                CMP     AL,AH                   ;Signs the same?
  580.                JZ      DIV_SIGN                ;Is yes, assumed right.
  581.                MOV     DL,"-"                  ;Else, negative.
  582. DIV_SIGN:      MOV     SAVE_SIGN,DL
  583.  
  584.                MOV     BP,DI                   ;BP -> divisor.
  585.                INC     BP                      ;Bump past sign.
  586.                MOV     DI,OFFSET WORKSPACE     ;Quotient scratch pad.
  587.                XOR     AL,AL
  588.                MOV     CX,SIZE_DIVIDEND
  589.                REP     STOSB                   ;Zero out quotient to start.
  590.  
  591.                MOV     DI,OFFSET DIVIDEND - 1  ;Move dividend to scratch pad.
  592.                STOSB                           ;Zero in overflow byte.
  593.                MOV     SI,BX                   ;Dividend.
  594.                INC     SI                      ;Bump past sign.
  595.                MOV     CX,SIZE_MANTISSA
  596.                REP     MOVSB
  597.                MOV     CX,SIZE_DIVIDEND - SIZE_MANTISSA
  598.                REP     STOSB                   ;Zero out dividend underflow.
  599.  
  600.                MOV     DI,BP                   ;Look for zero divisor.
  601.                MOV     CX,SIZE_MANTISSA
  602.                REPZ    SCASB
  603.                JNZ     DIVISOR_SIZE
  604.                MOV     DX,OFFSET ZERO_DIVIDE   ;Can't divide by zero.
  605.                POP     SI                      ;Command line pointer.
  606.                JMP     ERROR_EXIT
  607.  
  608. DIVISOR_SIZE:  MOV     DI,OFFSET WORKSPACE     ;Quotient.
  609.                ADD     DI,CX                   ;Point to first quotient storage.
  610.                ADD     BP,SIZE_MANTISSA - 1
  611.                SUB     BP,CX                   ;First non-zero divisor digit.
  612.                INC     CX                      ;DH = subtraction counter.
  613.                MOV     DX,CX                   ;DL = significant divisor digits.
  614.                MOV     SI,OFFSET DIVIDEND
  615.                MOV     CX,SIZE_DIVIDEND + 1
  616.                SUB     CX,DX                   ;Number of times to divide.
  617.  
  618. NEXT_DIVIDE:   CMP     MODULO_FLAG,1           ;Is this a modulo division?
  619.                JNZ     NO_MODULO
  620.                CMP     DI,OFFSET WORKSPACE + SIZE_MANTISSA
  621.                JAE     DIVIDE_END              ;If yes, divide integer only.
  622.  
  623. NO_MODULO:     PUSH    CX                      ;Division count.
  624.                PUSH    DI                      ;Quotient.
  625.                MOV     BX,SI                   ;Save dividend pointer.
  626.                XOR     DH,DH                   ;Initialize sub counter to zero.
  627.  
  628. NEXT_DIVIDE2:  CMP     BYTE PTR [SI - 1],0     ;Overflow in remainder?
  629.                JNZ     SUB_DIVISOR             ;If yes, divisor fits.
  630.                MOV     CL,DL                   ;Divisor length.
  631.                XOR     CH,CH                   ;Zero high half.
  632.                MOV     DI,BP                   ;Divisor.
  633. NEXT_DIV:      CMPSB                           ;Does it fit in remainder?
  634.                JA      SUB_DIVISOR             ;If yes, subtract.
  635.                JB      DIV_TOO_BIG             ;If no, save subtraction count.
  636.                LOOP    NEXT_DIV
  637.  
  638. SUB_DIVISOR:   INC     DH                      ;Sub counter.
  639.                MOV     SI,BP                   ;Divisor position.
  640.                MOV     DI,BX                   ;Dividend position.
  641.                MOV     CL,DL                   ;Significant divisor digits.
  642.                XOR     CH,CH                   ;Zero in high half.
  643.                DEC     CX                      ;Adjust for a moment.
  644.                ADD     SI,CX                   ;Point to current position.
  645.                ADD     DI,CX
  646.                INC     CX
  647.  
  648.                STD                             ;Subtract right to left.
  649.                CLC                             ;Start with no carry.
  650. NEXT_DIV_SUB:  LODSB
  651.                MOV     AH,AL                   ;Divisor digit.
  652.                MOV     AL,[DI]                 ;Remainder digit.
  653.                SBB     AL,AH                   ;Subtract.
  654.                AAS                             ;ASCII adjust.  (BCD)
  655.                STOSB                           ;Store in dividend.
  656.                LOOP    NEXT_DIV_SUB
  657.                SBB     BYTE PTR [DI],0         ;Subtract the carry, if any.
  658.  
  659.                CLD                             ;Back to forward.
  660.                MOV     SI,BX
  661.                JMP     NEXT_DIVIDE2            ;Next divisor.
  662.  
  663. DIV_TOO_BIG:   MOV     SI,BX                   ;Next dividend.
  664.                POP     DI
  665.                POP     CX
  666.                MOV     AL,DH                   ;Store quotient.
  667.                STOSB
  668.                INC     SI
  669.                LOOP    NEXT_DIVIDE
  670.  
  671.                MOV     DI,OFFSET WORKSPACE + SIZE_DIVIDEND - 1
  672.                MOV     CX,SIZE_MANTISSA
  673.                STD
  674.                CALL    ROUND                   ;Round quotient.
  675.                JC      DIVIDE_ERROR
  676.  
  677.                MOV     CX,SIZE_MANTISSA
  678.                MOV     SI,OFFSET WORKSPACE + SIZE_DIVIDEND - 2
  679.                MOV     DI,OPERAND              ;Store quotient in operand.
  680.                ADD     DI,CX
  681.                REP     MOVSB
  682.                MOV     AL,SAVE_SIGN            ;Add the sign.
  683.                STOSB
  684.  
  685.                MOV     CX,SIZE DECIMAL         ;Check for overflow.
  686. NEXT_OVER2:    LODSB
  687.                OR      AL,AL
  688.                JNZ     DIVIDE_ERROR
  689.                LOOP    NEXT_OVER2
  690.  
  691. DIVIDE_END:    POP     SI
  692.                POPF
  693.                POP     BP
  694.                RET
  695.  
  696. DIVIDE_ERROR:  MOV     DX,OFFSET OVERFLOW
  697.                POP     SI
  698.                JMP     ERROR_EXIT
  699.  
  700. ;----------------------------------------------;
  701. ; INPUT:  BX -> 1st and AH=sign; DI -> 2nd and AL=sign.
  702. ; OUTPUT: Result stored in DI.
  703.  
  704. MODULO:        PUSH    AX                      ;Save sign of dividend.
  705.                PUSH    DI                      ;Divisor pointer.
  706.                ADD     DI,SIZE INTEGER + 1
  707.                MOV     CX,SIZE INTEGER
  708.                STD
  709.                CALL    ROUND                   ;Round divisor.
  710.                JC      MODULO_ERROR
  711.                POP     DI
  712.                PUSH    DI
  713.                CALL    MAKE_INTEGER            ;Truncate to integer.
  714.  
  715.                MOV     DI,BX
  716.                ADD     DI,SIZE INTEGER + 1
  717.                MOV     CX,SIZE INTEGER
  718.                CALL    ROUND                   ;Round dividend.
  719.                JC      MODULO_ERROR
  720.                MOV     DI,BX
  721.                CALL    MAKE_INTEGER            ;Truncate to integer.
  722.  
  723.                POP     DI
  724.                MOV     MODULO_FLAG,1           ;Do modulo division.
  725.                CLD
  726.                CALL    DIVIDE
  727.                MOV     MODULO_FLAG,0
  728.                MOV     DI,OPERAND
  729.                POP     AX                      ;Sign of dividend.
  730.                MOV     AL,AH
  731.                STOSB                           ;Store it.
  732.                PUSH    SI
  733.                MOV     SI,OFFSET DIVIDEND      ;Dividend has remainder.
  734.                MOV     CX,SIZE_MANTISSA
  735.                REP     MOVSB                   ;Store it in operand.
  736.                POP     SI
  737.                RET
  738.  
  739. MODULO_ERROR:  MOV     DX,OFFSET OVERFLOW
  740.                JMP     ERROR_EXIT
  741.  
  742. ;----------------------------------------------;
  743. ; INPUT: DI -> Start of mantissa.
  744.  
  745. MAKE_INTEGER:  PUSHF
  746.                CLD
  747.                ADD     DI,SIZE INTEGER + 1
  748.                MOV     CX,SIZE DECIMAL
  749.                XOR     AL,AL
  750.                REP     STOSB
  751.                POPF
  752.                RET
  753.  
  754. ;----------------------------------------------;
  755. ; INPUT:  DI -> End of mantissa + 1; Direction flag reversed.
  756. ;         CX = number of bytes to round.
  757. ; OUTPUT: CY=1 if overflow.
  758.  
  759. ROUND:         CMP     BYTE PTR [DI],5         ; 5 or greater?
  760.                JB      ROUND_DONE              ;If no, done here.
  761.                DEC     DI                      ;Else, move left one digit.
  762.                STC                             ;Start with carry.
  763. NEXT_ROUND:    MOV     AL,[DI]
  764.                ADC     AL,0                    ;Add carry.
  765.                AAA                             ;ASCII adjust.
  766.                STOSB
  767.                JNC     ROUND_END               ;If no carry, done.
  768.                LOOP    NEXT_ROUND
  769.                JMP     SHORT ROUND_END
  770. ROUND_DONE:    CLC                             ;Return overflow.
  771. ROUND_END:     RET
  772.  
  773. ;----------------------------------------------;
  774. RESULT:        MOV     DX,OFFSET RESULT_MSG    ;Display "Result is ".
  775.                CALL    PRINT_STRING
  776.                MOV     SI,OPERAND              ;Point to result.
  777.                LODSB
  778.                OR      AL,AL                   ;Print sign if negative.
  779.                JZ      PRINT_INTEGER
  780.                CALL    PRINT_CHAR
  781.  
  782. PRINT_INTEGER: XOR     DH,DH                   ;Non-zero digit flag.
  783.                MOV     BL,3                    ;Comma every third digit.
  784.                MOV     CX,INTEGER_PRECISION
  785.                JMP     SHORT NEXT_INT2         ;Skip prefacing comma.
  786.  
  787. NEXT_INT:      OR      DH,DH                   ;Non-zero digit yet?
  788.                JZ      NEXT_INT2               ;If no, skip.
  789.                MOV     AX,CX                   ;Is position MOD 3 = 0?
  790.                DIV     BL
  791.                OR      AH,AH
  792.                JNZ     NEXT_INT2               ;If no, no comma.
  793.                MOV     AL,","                  ;Else, delimiting comma.
  794.                CALL    PRINT_CHAR
  795.  
  796. NEXT_INT2:     LODSB
  797.                OR      AL,AL                   ;Is digit zero?
  798.                JNZ     PRINT_INT               ;If no, print it.
  799.                OR      DH,DH                   ;Else, non-zero digit yet?
  800.                JZ      LOOP_INTEGER            ;If no, skip.
  801. PRINT_INT:     OR      DH,AL
  802.                CALL    PRINT_NUMBER
  803. LOOP_INTEGER:  LOOP    NEXT_INT
  804.  
  805.                PUSH    SI                      ;Save pointer.
  806.                MOV     CX,DECIMAL_PRECISION    ;Count the significant decimal
  807.                MOV     BX,CX                   ; places.
  808.                INC     BX
  809. NEXT_FRACTION: LODSB
  810.                OR      AL,AL
  811.                JZ      LOOP_FRACTION
  812.                MOV     BX,CX                   ;If non-zero, save count.
  813. LOOP_FRACTION: LOOP    NEXT_FRACTION
  814.                POP     SI                      ;Restore pointer.
  815.                NEG     BX                      ;Get count of significant.
  816.                ADD     BX,DECIMAL_PRECISION + 1
  817.                MOV     CX,DECIMAL_PLACES       ;Use the larger of significant
  818.                CMP     CX,BX                   ; digits; number entered or
  819.                JAE     DO_FRACTION             ; result.
  820.                MOV     CX,BX
  821.  
  822. DO_FRACTION:   JCXZ    CK_NULL                 ;If no significant, done here.
  823.                OR      DH,DH                   ;Else, was there a non-zero
  824.                JNZ     PRINT_DECIMAL           ; integer?
  825.                XOR     AL,AL                   ;If no, preface with zero.
  826.                CALL    PRINT_NUMBER
  827. PRINT_DECIMAL: MOV     AL,"."                  ;Display delimiting decimal.
  828.                CALL    PRINT_CHAR
  829.  
  830.                XOR     BL,BL                   ;Comma counter.
  831. NEXT_DEC:      CMP     BL,3
  832.                JNZ     NEXT_DEC2
  833.                MOV     AL,","
  834.                CALL    PRINT_CHAR
  835.                XOR     BL,BL
  836. NEXT_DEC2:     LODSB                           ;Display decimal digits.
  837.                CALL    PRINT_NUMBER
  838.                INC     BL
  839. LOOP_DEC:      LOOP    NEXT_DEC
  840.                JMP     SHORT RESULT_END
  841.  
  842. CK_NULL:       OR      DH,DH                   ;Was there a non-zero integer?
  843.                JNZ     RESULT_END              ;If yes, done here.
  844.                XOR     AL,AL                   ;Else, display solitary, zero.
  845.                CALL    PRINT_NUMBER
  846. RESULT_END:    MOV     DX,OFFSET CRLF          ;New line.
  847.                CALL    PRINT_STRING
  848.  
  849. ;----------------------------------------------;
  850. SAVE_ANSWER:   MOV     SI,OPERAND              ;Copy operand to answer.
  851.                MOV     DI,OFFSET ANSWER
  852.                MOV     CX,SIZE NUMBER
  853.                REP     MOVSB
  854.  
  855.                MOV     AH,30H                  ;Get DOS version.
  856.                INT     21H
  857.                CMP     AL,3                    ;Filename in environment
  858.                JB      TRY_CURRENT             ; not support before DOS 3.x.
  859.  
  860.                MOV     DS,DS:[2CH]             ;Segment of environment.
  861.                XOR     SI,SI
  862. FIND_ENV_END:  LODSB                           ;Find end of environment.
  863.                OR      AL,AL
  864.                JNZ     FIND_ENV_END
  865.                LODSB
  866.                OR      AL,AL
  867.                JNZ     FIND_ENV_END
  868.  
  869.                INC     SI                      ;Bump pointer past word count.
  870.                INC     SI
  871.                MOV     DI,OFFSET WORKSPACE     ;Copy our name.
  872.                CALL    COPY_NAME
  873.                PUSH    CS
  874.                POP     DS
  875.                MOV     DX,OFFSET WORKSPACE     ;Try to write answer.
  876.                CALL    WRITE_ANS
  877.                JNC     ANSWER_END
  878.  
  879. TRY_CURRENT:   MOV     DX,OFFSET COMPUTE       ;Try current directory.
  880.                CALL    WRITE_ANS
  881.                JNC     ANSWER_END
  882.  
  883. ; See if PATH= is in environment.
  884.                MOV     DS,DS:[2CH]             ;Segment of environment.
  885. LOOK_AT_PATH:  MOV     BX,-1                   ;Offset zero in environment.
  886. NEXT_PATH:     INC     BX
  887.                MOV     SI,BX
  888.                CMP     BYTE PTR [SI],0         ;Terminating null?
  889.                JNZ     PATH_SEARCH
  890.                CMP     BYTE PTR [SI+1],0       ;Double null?
  891.                JNZ     NEXT_PATH
  892.                JMP     SHORT CANT_SAVE         ;If so, end of environment.
  893.  
  894. PATH_SEARCH:   MOV     DI,OFFSET PATH          ;Search for "PATH=".
  895.                MOV     CX,PATH_LEN
  896.                REP     CMPSB
  897.                JNZ     NEXT_PATH
  898.  
  899. ; PATH= found; search for paths.
  900. NEXT_STRING:   CMP     BYTE PTR [SI],0         ;Terminating null?
  901.                JZ      CANT_SAVE
  902.                CMP     BYTE PTR [SI-1],0       ;Double null?
  903.                JZ      CANT_SAVE
  904.  
  905. MOVE_STRING:   MOV     DI,OFFSET WORKSPACE     ;Get path.
  906. NEXT_BYTE:     LODSB
  907.                OR      AL,AL
  908.                JZ      SEARCH_PATH
  909.                CMP     AL,";"
  910.                JZ      SEARCH_PATH
  911.                STOSB
  912.                JMP     NEXT_BYTE
  913.  
  914. SEARCH_PATH:   CALL    COPY_PATH               ;Create filespec.
  915.                PUSH    DS
  916.                PUSH    CS
  917.                POP     DS
  918.                MOV     DX,OFFSET WORKSPACE
  919.                CALL    WRITE_ANS               ;Try to write answer.
  920.                POP     DS
  921.                JC      NEXT_STRING             ;If failed, try next path.
  922.                JMP     SHORT ANSWER_END        ;Else, done.
  923.  
  924. CANT_SAVE:     PUSH    CS
  925.                POP     DS
  926.                MOV     DX,OFFSET NOT_FOUND     ;Answer write error message.
  927.                CALL    PRINT_STRING
  928.  
  929. ANSWER_END:    RET
  930.  
  931. ;----------------------------------------------;
  932. COPY_NAME:     LODSB
  933.                STOSB
  934.                OR      AL,AL
  935.                JNZ     COPY_NAME
  936.                RET
  937.  
  938. ;----------------------------------------------;
  939. COPY_PATH:     PUSH    SI
  940.                PUSH    DS
  941.                CMP     DI,OFFSET WORKSPACE
  942.                JZ      MOVE_NAME
  943.                CMP     BYTE PTR ES:[DI-1],"\"  ;Filespec delimiter?
  944.                JZ      MOVE_NAME
  945.                CMP     BYTE PTR ES:[DI-1],":"
  946.                JZ      MOVE_NAME
  947.                MOV     AL,"\"                  ;If no, add one.
  948.                STOSB
  949.  
  950. MOVE_NAME:     PUSH    CS
  951.                POP     DS
  952.                MOV     SI,OFFSET COMPUTE       ;Tack on "COMPUTE.COM".
  953.                MOV     CX,COMPUTE_LEN
  954.                REP     MOVSB
  955.  
  956. COPY_END:      POP     DS
  957.                POP     SI
  958.                RET
  959.  
  960. ;----------------------------------------------;
  961. ; INPUT:  DX -> Compute.com filespec.
  962. ; OUTPUT: CY=0 if successful; CY=1 if failed.
  963.  
  964. WRITE_ANS:     PUSH    SI
  965.                MOV     AX,3D02H                ;Open for reading and writing.
  966.                INT     21H
  967.                JC      WRITE_END
  968.                MOV     BX,AX
  969.                MOV     DX,OFFSET WORKSPACE
  970.                MOV     CX,SIZE WORKSPACE
  971.                MOV     AH,3FH                  ;Read signature.
  972.                INT     21H
  973.                JC      WRITE_END
  974.  
  975.                MOV     SI,100H                 ;Make sure we found correct
  976.                MOV     DI,OFFSET WORKSPACE     ; Compute.com.
  977.                REP     CMPSB
  978.                STC
  979.                JNZ     WRITE_END
  980.  
  981.                XOR     CX,CX
  982.                MOV     DX,OFFSET ANSWER - 100H
  983.                MOV     AX,4200H                ;Seek offset of answer.
  984.                INT     21H
  985.                JC      WRITE_END
  986.  
  987.                MOV     DX,OFFSET ANSWER
  988.                MOV     CX,SIZE ANSWER
  989.                MOV     AH,40H                  ;Write answer to disk.
  990.                INT     21H
  991.                JC      WRITE_END
  992.  
  993.                MOV     AH,3EH                  ;Close file.
  994.                INT     21H
  995. WRITE_END:     POP     SI
  996.                RET
  997.  
  998. ;----------------------------------------------;
  999. PRINT_NUMBER:  ADD     AL,"0"                  ;Binary to ASCII.
  1000. PRINT_CHAR:    MOV     DL,AL
  1001.                MOV     AH,2                    ;Print char via DOS.
  1002.                INT     21H
  1003.                RET
  1004.  
  1005. ;----------------------------------------------;
  1006. PRINT_STRING:  MOV     AH,9                    ;Print string via DOS.
  1007.                INT     21H
  1008.                RET
  1009.  
  1010. ;----------------------------------------------;
  1011. ; HEAP
  1012.  
  1013. INITIAL_VALUE  NUMBER  <>
  1014.  
  1015. _TEXT          ENDS
  1016.                END     START
  1017.