;PROGRAM:  PROG2.ASM
;FOR:      CS 140 Section 4 David Hayes
;BY:       Howard Scholz
;DATE:     March 20, 1995
;FUNCTION: Prime Number Program.
;          This program presents the user with a menu, with two
;          main choices and an exit choice.  The first option allows
;          the user to enter a number, and then determines if the
;          number is prime.  The second choice shows all prime numbers
;          within a range provided by the user.
;WARNINGS:
;COMMENTS:
;CALLS:    DOS INT 21 func 9    is used to display text.
;          DOS INT 21 func 0ah  is used to receive text input.
;          DOS INT 21 func 04ch is used to exit to DOS.
;          DOS INT 10 func 06h  is used to clear the screen.
;INPUT:    The user is promted for his/her choice:
;            Choice 1 - The user is prompted for a number to check.
;            Choice 2 - The user is prompted for two numbers (a range).
;OUTPUT:     Choice 1 - If prime, a simple statement asserting this fact.
;                     - If composite, a list of prime factors are given.
;            Choice 2 - All primes withing the range are displayed,
;                         5 numbers to a line.
;HISTORY:  Written by Howard E Scholz 4 March 1995 
;          22 April 1995 Added dosprnt, and dosinp macros to shorten src
;                        Added input check for asc2bin
;                        Added INT 10h clear screen
;*************** PARAMETER DECLARATIONS ****************************


;*************** STACK SEGMENT *************************************

stack   segment para    stack
        dw      2048    dup(0)
stack   ends
;
;*************** DATA SEGMENT **************************************
;*******************************************************************

data    segment PARA

intro   db      10,10,13,'Prime Number Evaluator (PROG2.ASM)',10,13,10,13
        db      '  This program determines if a given number is prime, and if not, lists all',10,13
        db      'of the prime factors.  It also allows the entry of a range, and displays all',10,13 
        db      'of the primes within that range.',10,13,'$'
menu    db      10,13,10,13,'-=* Prime Number Evaluator Menu *=-',10,13,10,13
        db      '    1 - Evaluate a number',10,13
        db      '    2 - Display primes within a range',10,13
        db      '    3 - Exit to DOS',10,13,10,13
        db      'Enter choice (1-3) : ','$'
crlf    db      10,10,13,'$'
err1    db      10,13,'Invalid Menu Choice.  Please try again.',10,13,'$'
err2    db      10,13,'End of range must be greater than start!',10,13,'$'
err3    db      10,13,'Invalid entry. Try again',10,13,'$'
prompt1 db      10,13,'Enter number to be evaluated (1-65535) : ','$'
prompt2 db      10,13,'Enter start of range (1-65535) : ','$'
prompt3 db      10,13,'Enter end of range (1-65535)   : ','$'
prompt4 db      10,13,'Press  to display more primes','$'
prompt5 db      10,13,10,13,'Press  to return to main menu','$'

rpt1    db      10,13,10,13,'Yes, that is a prime number',10,13,'$'
rpt2    db      10,13,10,13,'No, one is not a prime number',10,13,'$'
rpt3    db      10,13,10,13,'No, that is not a prime number.  The factors are :',10,13,'$'
rpt4    db      10,13,10,13,'The primes in that range are :',10,13,'$'
ascnum  db      16      dup(20h)        ; Space for bin2asc conversion
        db      '$'

choice  db      10                      ; Space for menu choice
        db      12      dup(?)
num1    db      10                      ; Reserve Space for text entry
        db      12      dup(?)
num2    db      10                      ;  of numbers
        db      12      dup(?)

primect dw      3                       ; Number of Primes in the data list
primes  dw      2,3,5                   ; Prime number data list
        dw      6700    dup(?)

factors dw      128     dup(?)          ; A list of factors for a number

data    ends
;
;*************** MACROS ********************************************

dosprnt MACRO   txtptr:REQ
        MOV     DX,offset txtptr        ; Point to text
        MOV     AH,9                    ; DOS service 9
        INT     21h                     ; Print the text
ENDM

dosinp  MACRO   txtptr:REQ
        MOV     DX,offset txtptr        ; Point to text buffer
        MOV     AH,0ah                  ; DOS service 0ah
        INT     21h                     ; Input the text
ENDM

;
;*************** CODE SEGMENT **************************************

code    segment PARA
        assume  cs:code,ds:data,ss:stack
start:  MOV     AX,DATA                 ; Standard data segment
        MOV     DS,AX                   ;  initialization.
        MOV     ES,AX                   ; Using DI
        dosprnt intro                   ; Display intro
main:   dosprnt menu                    ; Display menu
        dosinp  choice                  ; Get menu choice
        CALL    asc2bin                 ; Get binary of choice in AX
        JNC     main2
        JMP     main5                   ; Overflow has occured. Display err
main2:  CMP     AX,1                    ; Menu Choice 1 ?
        JNZ     main3
        JMP     main10                  ; Yes - Goto Choice 1 section
main3:  CMP     AX,2                    ; Menu Choice 2 ?  
        JNZ     main4
        JMP     main20                  ; Yes - Goto Choice 2 section
main4:  CMP     AX,3                    ; Menu Choice 3 ?
        JNZ     main5
        MOV     AX,0600h                ; AH 06 (scroll),AL 00 (full screen)
        MOV     BH,07                   ; Attribute: white(7) on black (0)
        MOV     CX,0                    ; Upper left row:column
        MOV     DX,184fh                ; Lower right row, column
        INT     10h                     ; Clear the screen
        JMP     done
main5:  dosprnt err1                    ; Invalid menu choice
        JMP     main                    ; Return to main menu

;************************************************************************
; Prompt the user for a number to check if prime
;************************************************************************

main10: dosprnt prompt1                  ; Display prompt
        dosinp  num1                     ; Get number to check    
        CALL    asc2bin                  ; Get binary of number in AX
        JNC     @F
        dosprnt err3                    ; Invalid entry
        JMP     main10
@@:     MOV     DX,1                    ; Menu Choice 1
        CALL    primchk                 ; Check if prime
        CMP     CX,0FFFFh               ; Chk if One was entered - not prime
        JZ      main12                  ; One was entered
        CMP     AX,[BX]                 ; Was it in the table?
        JZ      mn111                   ; Yes - prime
        CMP     CX,0                    ; Not one, but was it prime?
        JNZ     main13                  ; No
mn111:  dosprnt rpt1                    ; Yes, prime entered, report to user
        JMP     main                    ; Go back to menu
main12: dosprnt rpt2                    ; Report one is not a prime number
        JMP     main                    ; Go back to menu
main13: PUSH    CX                      ; Save prime count
        dosprnt rpt3                    ; Composite, say so and...
        POP     CX                      ;Show the factors!!!!
        MOV     BX,offset factors       ; point to first factor
main15: MOV     AX,[BX]                 ; Get factor in AX
        PUSH    CX
        PUSH    BX
        MOV     BX,offset ascnum
        CALL    bin2asc                 ; Convert to ASCII
        dosprnt ascnum                  ; Show the factor
        POP     BX
        POP     CX
        ADD     BX,2                    ; point to next factor
        LOOP    main15                  ; do it again.
        dosprnt crlf                    ; skip a line
        JMP     main                    ; Go back to menu

;************************************************************************
; Prompt the user for a range to list primes
;************************************************************************

main20: dosprnt prompt2                 ; Display prompt
        dosinp  num1                    ; Get first number (beg of range)
        CALL    asc2bin                 ; Get binary of number in AX
        JNC     @F
        dosprnt err3                    ; Invalid entry.  Try again
        JMP     main20
@@:     PUSH    AX                      ; Save beginning of range
main21: dosprnt prompt3                 ; Display prompt
        dosinp  num1                    ; Get Second number (end of range)
        CALL    asc2bin                 ; Get binary of number in AX
        JNC     @F
        dosprnt err3                    ; Invalid entry.  Try again.
        JMP     main21
@@:     POP     BX                      ; Get beginning of range
        CMP     BX,AX                   ; Is beginning > end ?
        JBE     main22                  ; No, all is okay
        dosprnt err2                    ; Display error msg
        JMP     main20                  ; Get # again
main22: MOV     DX,2                    ; Menu Choice 2
        CALL    primchk                 ; Get pointer to first prime, & cnt
        PUSH    CX                      ; Save count
        PUSH    DI                      ; Save pointer
        dosprnt rpt4                    ; "Here are the primes in that range"
        POP     SI
        POP     CX
        MOV     AX,CX                   ; Display 110 primes per round
        XOR     DX,DX
        MOV     DI,110
        DIV     DI
        PUSH    DX
        MOV     CX,AX
        CMP     CX,0                    ; Less than 110 primes?
        JE      main26                  ; Skip normal rounds
main23: PUSH    CX
        MOV     CX,110                  ; Display 110 primes
main25: MOV     AX,[SI]                 ; Get prime in AX
        MOV     BX,offset ascnum        ; Point to ascnum buffer
        PUSH    CX                      ; Save count
        PUSH    SI                      ; Save ptr to prime
        CALL    bin2asc
        dosprnt ascnum                  ; Display the number
        POP     SI                      ; Restore ptr
        POP     CX                      ; Restore loop counter
        ADD     SI,2                    ; Point to next prime
        LOOP    main25
        dosprnt prompt4                 ; Press ENTER for more primes
        dosinp  choice                  ; Wait for enter key
        POP     CX
        LOOP    main23                  ; Display another 100 primes
main26: POP     CX
main27: MOV     AX,[SI]                 ; Get prime in AX
        MOV     BX,offset ascnum        ; Point to ascnum buffer
        PUSH    CX                      ; Save count
        PUSH    SI                      ; Save ptr to prime
        CALL    bin2asc
        dosprnt ascnum                  ; Display the number
        POP     SI                      ; Restore ptr
        POP     CX                      ; Restore loop counter
        ADD     SI,2                    ; Point to next prime
        LOOP    main27                  ; Loop until done
        dosprnt prompt5                 ; Press enter to return to main menu
        dosinp  choice                  ; Wait for enter key
        JMP     main                    ; Go back to main menu
done:   MOV     AX,4c00H                ; Exit to DOS
        INT     21H


;************************************************************************
; Procedure: asc2bin 
; Converts ASCII number in buffer pointed to by DX,
; Buffer congruent with DOS INT 21 func 0ah format
;  DX ---> Max Count
;  DX+1 -> Actual Count
;  DX+2 -> ASCII characters read
; Returns binary number in AX
; Zero's CX, sets SI to 10
; Trashes DI and BX
; Sets carry flag on overflow
; Checks for non-numeric input and sets carry flag, if so.
;************************************************************************
asc2bin PROC    NEAR
        MOV     BX,DX
        INC     BX              ; Point BX to char count
        XOR     CX,CX
        MOV     CL,[BX]         ; Move char count to CX for loop
        XOR     AX,AX           ; Initialize AX
        XOR     DI,DI           ;
        MOV     SI,10           ; Base 10 Multiplier
        CMP     CX,0            ; Zero Characters entered?
        JNZ     asclp           ;   No, go to loop
        JMP     ascdn           ;   Yes, assume zero, and exit
asclp:  INC     BX              ; Point to next character, or first
        MUL     SI              ; Muliply AX by 10
        JC      ascdn           ;  Exit on Overflow
        MOV     DI,AX           
        MOV     AL,[BX]         ; Get next character
        CMP     AL,'0'          ; Is char less than '0' ?
        JL      ascerr          ; Yes - error
        CMP     AL,'9'          ; Is char gtr than '9' ?
        JG      ascerr          ; Yes - error
        SUB     AL,30h          ;   Convert digit to binary
        CBW                     ;   Convert byte to word
        ADD     AX,DI
        JC      ascdn           ; Exit on Overflow
        LOOP    asclp
ascdn:  RET
ascerr: STC                     ; Set error flag
        RET

asc2bin ENDP

;************************************************************************
; Procedure : BIN2ASC
; Converts binary number in AX to ASCII
; Stores result in buffer pointed to by BX
; Returns pointer to first char in BX
; Trashes DX, Zero's AX
; Sets CX to 10, SI to 2020
;************************************************************************
bin2asc PROC    NEAR
        ADD     BX,10
        MOV     [BX],2020h        ; Clear leading spaces
        ADD     BX,2              
        MOV     [BX],2020h        ; Clear leading spaces
        ADD     BX,2
        MOV     [BX],2020h        ; Clear leading spaces
        ADD     BX,2              ; Point to end of buffer
        MOV     CX,10           ; Base 10 divisor
binlp:  DEC     BX              ; Point to next position
        XOR     DX,DX           ; Working with a single prec number
        DIV     CX
        ADD     DL,'0'          ; Convert remainder to ASCII
        MOV     [BX],DL         ; Store result in buffer
        CMP     AX,0            ; Are we done yet ?
        JNZ     binlp           ; No - calculate next digit
        RET
bin2asc ENDP

;************************************************************************
; Procedure : primchk
; Input     : BX as beginning of range
;             AX as end of range (or number to check)
;             DX as option number (1 or 2)
; Assumes   : AX > BX, and AX <> 0
; Returns   : Option 1 - BX as pointer to list of factors
;                        CX as count of factors
;             Option 2 - BX as pointer to list of primes
;                        CX as count of primes
; Trashes   : Everything else: AX, DX, SI, DI, ES, Flags
;************************************************************************
primchk PROC    NEAR
        PUSH    AX
        PUSH    BX
        PUSH    DX              ; Save parameters

primno: MOV     DI,[word ptr primect]    ; Get number of primes in DI
        DEC     DI
        SHL     DI,1           ; *2 because words = 2 bytes
        CMP     AX,primes[DI]   ; Is high number > last prime
        JA      prmbld          ; Yes - build primes
        JMP     goopt           ; No - branch to option

prmbld: MOV     CX,AX           ; Get hi-test in CX
        MOV     AX,primes[DI]   ; Get last prime in AX
        SUB     CX,AX           ; Get count of number btwn last prm & hi-test
        JC      goopt           ; Count < 0, skip build
        SHR     CX,1            ; Adding two for each loop
        INC     CX
bldlp:  ADD     AX,2            ; Add two to number to be tested
        PUSH    CX              ; Save CX
        
;**** Beginning of inner test loop

        CMP     AX,1            ; Is number to be checked a 1?
        JNZ     bld1            ; No - Check for primes
        MOV     CX,0FFFFh       ; Set One flag
        JMP     bld6            ; Exit - Special Case
bld1:   MOV     CX,[word ptr primect]    ; Get prime count
        MOV     BX,offset primes ; Point to first prime
        MOV     SI,AX           ; Save number to be tested in SI
bld2:   XOR     DX,DX           ; prepare for division
        MOV     DI,CX           ; Save CX
        MOV     CX,[BX]         ;   Do the division test!!
        DIV     CX

        CMP     AX,CX           ; Only need to test to square root of #
        JGE     bld3            ; Quotient >= Divisor, continue
        CMP     DX,0            ; Divide to zero?
        JZ      bld3            ; Yes - not prime
        MOV     CX,DI           ; Restore loop counter
        MOV     AX,SI           ; Restore number to be tested
        SHL     CX,1            ; Multiply by two
        ADD     BX,CX           ; Point BX to next open spot
        XOR     CX,CX           ; CX = 0, prime
        JMP     bld6            ; Done

bld3:   MOV     CX,DI           ; Restore loop counter
        MOV     AX,SI           ; Restore number to be tested
        CMP     DX,0            ; Was there a remainder after division?
        JZ      bld6            ; No - this is not a prime
        ADD     BX,2            ; Point to next prime in table
        LOOP    bld2            ; Test next prime
                                ; Done - 0 in CX = prime, and BX-> next open
                                ;    0FFFFh      = one
                                ;    otherwise, BX points to a factor

;**** End of inner build test loop

bld6:   CMP     CX,0            ; assume case of one will not happen in build
        JNZ     ntprm           ; Not prime, do not add
        MOV     [BX],AX         ; Add prime to table
        MOV     BX,[word ptr primect]    ; Get count of primes
        INC     BX              ; Add one - new prime found
        MOV     [word ptr primect],BX    ; Store
ntprm:  POP     CX              ; Restore loop counter
        LOOP    bldlp           ; Test next number

goopt:  POP     DX
        POP     BX
        POP     AX              ; Restore parameters
        CMP     DX,2            ; Was option 2 chosen?
        JE      opt2            ; Yes, goto range check
                                ; No, check if number prime
        
;******* Option One Section - Check if a number is prime

opt1:   CMP     AX,1            ; Is number to be checked a 1?
        JNZ     notone          ; No - Check for primes
        MOV     CX,0FFFFh       ; Set One flag
        JMP     notprm          ; Exit - Special Case
notone: MOV     CX,[word ptr primect]    ; Get prime count
        MOV     BX,offset primes ; Point to first prime
        MOV     SI,AX           ; Save number to be tested in SI
Isprlp: XOR     DX,DX           ; prepare for division
        MOV     DI,CX           ; Save CX
        MOV     CX,[BX]         ;   Do the division test!!
        DIV     CX
Ispr2:  MOV     CX,DI           ; Restore loop counter
        MOV     AX,SI           ; Restore number to be tested
        CMP     DX,0            ; Was there a remainder after division?
        JZ      notprm          ; No - this is not a prime
        ADD     BX,2            ; Point to next prime in table
        LOOP    Isprlp          ; Test next prime
                                ; Done - 0 in CX = prime, and BX-> next open
                                ;    0FFFFh      = one
                                ;    otherwise, BX points to a factor
                                ; ******* End of prime check 
notprm: CMP     CX,0FFFFh       ; Was number a one?
        JZ      opt1dn
        CMP     AX,[BX]         ; Was it in the table?
        JNZ     op1bld          ; No - build factor table
        XOR     CX,CX           ; Yes - it was prime, clear CX
        JMP     opt1dn
                                ; Build factor table - first factor in [BX]
op1bld: MOV     SI,offset factors
        XCHG    BX,SI           ; BX --> Factor table, SI ---> primes
        MOV     DX,[SI]       ; Get first factor in DX
        MOV     [BX],DX       ; Put factor in table
        ADD     BX,2            ; Point to next open spot in factor table
        MOV     CX,1          ; Count of factors
op1bd2: MOV     DI,AX           ; Save AX
        XOR     DX,DX           ; Prepare for divide
        DIV     word ptr [SI]
        CMP     DX,0            ; Even divide ?
        JNZ     op1bd3          ; No, try next factor
        CMP     AX,1            ; Divide to 1?
        JNZ     op1bd2          ; No, try this factor again
        JZ      opt1dn          ; Yes, done
op1bd3: MOV     AX,DI           ; Restore AX
        ADD     SI,2            ; Point to next prime
        XOR     DX,DX
        DIV     word ptr [SI]
        CMP     DX,0            ; Even divide ?
        JNZ     op1bd3          ; No, try next factor
        MOV     DX,[SI]         ; Get factor in DX
        MOV     [BX],DX         ; Add to table
        INC     CX              ; Increment factor counter
        ADD     BX,2            ; Point to next open spot in factor table
        CMP     AX,1            ; Divide to 1?
        JZ      opt1dn           ; Yes, done
        JMP     op1bd2          ; No, try this factor again
opt1dn: RET

opt2:   MOV     SI,offset primes
        XOR     DX,DX           ; Set count of primes to zero
        MOV     CX,[word ptr primect]
opt21:  CMP     BX,[SI]         ; Is beginning of range >= prime
        JB      opt22           ; No, get count
        ADD     SI,2            ; Yes, try next prime
        DEC     CX
        JMP     opt21
opt22:  MOV     DI,SI           ; Pointer to first prime in DI
opt23:  INC     DX              ; Increment primes counter
        CMP     AX,[SI]         ; Is end of range >= prime
        JBE     opt24           ; No - done
        ADD     SI,2            ; Yes, try next prime
        LOOP    opt23           ; Do it until all out of primes
opt24:  MOV     SI,DI           ; SI --> first prime, CX = count
        MOV     CX,DX
        RET                     

primchk ENDP

code    ends
        end     start