

; ASK program to enable you to ask questions and let users type in a
; one-character response. This allows you to build simple menu-driven
; batch file or yes/no type questions.
;
; See the external docuementation for more details.
; Written by Peter Wu; April, 1986
; UUCP: {seismo|ihnp4|harvard|ucbvax|allegra|topaz}!uwvax!uwmacc!pwu
; ARPA: pwu@unix.macc.uwisc.edu
;
argl    equ     80h             ; start of command line
special equ     '^'             ; the special escape character
switch  equ     '/'             ; switch character preceeding options
cmdlen  equ     150             ; max length of converted cmd line
maxargc equ     4               ; max # of arguements
squote  equ     39              ; ASCII of '

all     segment 'code'
        assume  cs:all,ds:all
        org     100h
entry:  jmp     start

;********************** data area ***********************
line    db      cmdlen dup(?)   ; store the converted command line
argc    db      ?
argv    dw      maxargc * 2 dup(?)      ; *argv[]
option  db      1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1
              ; a b c d e f g h i j k l m n o p q r s t u v w x y z
                ; 0 = option off, 1=invalid option, 2 = option on
cry     db      'unknown option ',squote
unkopt  db      ?               ; fill in the unknown option here
        db      squote,' ignored',13,10,'$'

;*********************** code area **********************
; getc returns a converted character. bx should point to command line
; cl should contain the length of the command line
; ah=0 if normal character; ah='\' if special character.

three   db      ?               ; for counting down from 3
ten     db      10              ; for multiplying

getc    proc    near
        xor     ax,ax           ; clear character
        sub     cl,1            ; len = len - 1
        jc      eoln            ; reached end of command line

        mov     al,[bx]         ; get a character
        inc     bx              ; point to next one
        cmp     al,special      ; is it the special char '\' ?
        jnz     normal          ; nop; just a normal character

        sub     cl,1            ; len = len - 1
        jc      eoln            ; last character is '\'; ignore it

        mov     ah,al           ; flag special char
        mov     al,[bx]         ; get the next char
        inc     bx              ; point to next char

; translate \nnn characters into ascii
        cmp     al,'0'          ; see if first n is a digit
        jb      notdig          ; not a digit
        cmp     al,'9'
        ja      notdig          ; not a digit

; first n is a digit
        xor     ch,ch           ; init accumulator
        mov     three,3         ; convert 3 digits at most

digit:
        sub     al,'0'          ; convert to binary
; now multiply cl by ten and add al to it
        xchg    al,ch           ; do this so we can use al to multiply
        mul     ten             ; assume result is still 8 bits
        add     ch,al           ; one digit is done.

; more digits?
        dec     three
        jz      gotit           ; converted all three digits
        mov     al,[bx]         ; examine next char
        cmp     al,'0'          ; is it a digit
        jb      gotit           ; not a digit; end translation
        cmp     al,'9'
        ja      gotit           ; not a digit; end translation
        inc     bx              ; point to next char
        jmp     digit           ; process digit

gotit:
        mov     ah,special      ; flag special char even for \nnn
        mov     al,ch           ; return char in al
        ret
normal:
        ret

notdig:                         ; not \nnn (e.g. \a \b \c \d ...)
        cmp     al,special      ; \\ => \
        je      skip
        cmp     al,'"'          ; \" => "
        je      skip
        and     al,31           ; \a => ctrl-a; \b => ctrl-b; ...
skip:
        ret

eoln:
        xor     ax,ax           ; return end-of-line char
        ret

getc    endp

;*********************** prts **********************
count   dw      ?
p       dw      ?

prts    proc    near            ; print string to stdout; si points to string
        mov     ax,[si+2]       ; pointer to string
        mov     p,ax            ; save pointer
        mov     ax,[si]         ; get length of string
        mov     count,ax

ploop:  sub     count,1
        jc      d1              ; end of string
        mov     bx,p
        mov     dl,[bx]         ; current character to be printed
        inc     p               ; point to next character
        mov     ah,2            ; function number for print character
        int     21h             ; print character
        jmp     ploop           ; repeat

d1:     ret
prts    endp

;********************* main *********************

mc      db      ?               ; loop index
char    db      ?               ; key pressed by user

crlf    db      13,10,'$'       ; crlf string

helpms  db      'Usage:',13,10
        db      '  ask [',switch,'c',switch,'q] '
        db      '"prompt message" "expected response"',13,10,10
        db      '    ',switch
        db      'c makes the response case sensitive',13,10
        db      '    ',switch,'q will accept non-expected response (returns'
        db      ' 0)',13,10
        db      '    "prompt message" and "expected response" are quoted'
        db      ' strings',13,10
        db      '    To embed special characters use ',special
        db      'nnn where nnn is a 3-digit ASCII',13,10
        db      '    or ',special,'a for ctrl-a, ',special,'b for'
        db      ' ctrl-b, ... (except ',special,special,' for ',special
        db      ' itself',13,10,'    and ^" for the quote character)',13,10
        db      '  Response can be tested with if errorlevel',13,10
        db      '  Errorlevel is set to index("expected response",key '
        db      'pressed)',13,10,10
        db      'See the external document for more information',13,10
        db      '$'
nom     db      '*BEEP*  Unexpected response',13,10,10,'$'

start   proc    near

        mov     bx,argl         ; start of parameters
        mov     cl,[bx]         ; length of parameter
        inc     bx              ; now point to first char
        mov     di,offset line  ; point to convert line buffer
        mov     si,offset argv - 4      ; point to *argv[0] - 4

        mov     argc,0          ; normally start at 1; but for ask.com 0 is ok

; now split command line into arguements in argv[]
loop:
        cmp     argc,maxargc    ; can we handle more arguements?
        jae     done            ; if not; get out

        call    getc            ; convert one character
        or      ax,ax           ; check for done
        jz      done            ; end of command line

        cmp     al,' '          ; a blank space?
        jz      loop            ; skip it

; now we must be seeing the beginning of an arguement
        inc     argc            ; count it
        add     si,4            ; point to next *argv[]
        mov     [si+2],di       ; where string start
        mov     word ptr [si],0 ; set length to 0

        mov     dx,' '          ; assume this is the separator
        cmp     ax,'"'          ; start of quoted string?
        jne     noquote         ;
        mov     dx,'"'          ; make this the separator
        jmp     next            ; don't store the first "

noquote:
        mov     [di],al         ; store the character
        inc     di
        inc     word ptr [si]   ; increment argv's length
next:
        call    getc
        or      ax,ax
        jz      done            ; end of arguement

        cmp     ax,dx           ; is this the separator?
        jz      loop            ; find next arguement

        jmp     noquote         ; keep storing

; now done with splitting argv[]'s
done:
; now let's check for options
        mov     ah,argc
        mov     mc,ah           ; for mc = argc downto 1 do
        mov     si,offset argv - 4
        or      ah,ah           ; do we any argv at all?
        jz      endopt
more:
        add     si,4            ; point to *argv[0,1,2,...]
        mov     bx,[si+2]       ; address of string
        cmp     byte ptr [bx],switch    ; is this string an option?
        jnz     endopt          ; no more options

; process this option (option can be /abc or /a /b /c or /a/b/c)
        mov     cx,[si]         ; length of option string
mopts:
        inc     bx              ; point to char after the switch
        dec     cx              ; decrement length of option string
        jz      nextop          ; done with this option string. Next.

; now scrutinize the option letter
        mov     al,[bx]         ; the option letter
        cmp     al,switch       ; is the switch embedded in here?
        je      mopts           ; ignore it

        cmp     al,'A'          ; check for legal option letters
        jb      badopt
        cmp     al,'Z'
        jbe     goodopt         ; a valid uppercase option letter

        cmp     al,'a'
        jb      badopt
        cmp     al,'z'
        ja      badopt

goodopt:
        mov     dl,al           ; save option letter in case of error
        and     al,31           ; convert to 1-26
        dec     al              ; convert to 0-25
        xor     ah,ah
        mov     di,ax           ; use as an index
        mov     al,dl           ; in case we need this to print error
        cmp     option[di],1    ; check if this is a known option
        je      badopt          ; if not, yell at user
        mov     option[di],2    ; set the option
        jmp     mopts           ; go look at next option char.

badopt:                         ; yell at user for bad option character
        mov     unkopt,al
        mov     dx,offset cry
        mov     ah,9            ; print the error message
        int     21h
        jmp     mopts           ; look at next option char.

nextop:
        dec     mc              ; count down argc
        jnz     more

; now process the parameters
endopt:
        cmp     mc,0            ; see if there's any arguements at all
        ja      nohelp
        mov     dx,offset helpms        ; if not, print a summary
        mov     ah,9            ; print string
        int     21h
        xor     al,al           ; return code 0
        jmp     die

nohelp:                         ; si is pointing to a parameter *argv[]
        mov     di,si
        add     di,4            ; if mc = 2 then di is pointing to the
                                ;  expected response string's argv[]

; now see if we should convert the "expected response" to upper case
        cmp     mc,2            ; is there an "expected response" at all?
        jb      ask             ; if not, then forget about converting
        cmp     option + 'c' - 'a',2    ; should we be case sensitive?
        je      ask             ; if so, then forget about converting

; convert "expected response" to upper case
        mov     cx,[di]         ; length of "expected response"
        mov     bx,[di+2]       ; point to the actual string

convert:
        mov     al,[bx]         ; get a character from "expected response"
        cmp     al,'a'          ; see if it's a lower case letter
        jb      notlow
        cmp     al,'z'
        ja      notlow

; alright, we have a lower case letter here, make it upper and store it back
        sub     al,32
        mov     [bx],al

notlow:
        inc     bx              ; point to next char
        dec     cx              ; decrement length
        jnz     convert         ; until all characters are converted

ask:
        call    prts            ; print first parameter (the prompt)
        mov     ah,1            ; keyboard input function code
        int     21h             ; get a char
        mov     char,al         ; save it

        mov     dx,offset crlf  ; print a carriage return
        mov     ah,9
        int     21h

; now see if the /c option is on or off
        mov     al,char         ; get it back
        cmp     byte ptr [option+'c'-'a'],2
        je      nocase          ; no case translation if on

; translate to upper case
        cmp     al,'a'
        jb      nocase
        cmp     al,'z'
        ja      nocase
        sub     al,32           ; convert lower case to upper case

nocase:
        cmp     mc,2            ; test if expected response is present
        jb      die             ; if not present, just return the ASCII

; now we should try to search the key pressed with expected response
        mov     cx,[di]         ; length of string
        mov     bx,[di+2]       ; start of string
        mov     ah,al           ; mov char into ah
        xor     al,al           ; use this to keep track of the index

search:
        inc     al              ; adjust index
        cmp     [bx],ah         ; match ?
        je      die             ; if so, exit with index as errorlevel
        inc     bx              ; point to next char
        dec     cx              ; more to check?
        jnz     search

; ok, no match, what should we do?
        xor     al,al           ; return this if quiet option is set
        cmp     option + 'q' - 'a',2    ; quiet option set?
        je      die             ; return with errorlevel = 0

        mov     dx,offset nom   ; quiet option not set, so yell at user!
        mov     ah,9
        int     21h
        jmp     ask             ; prompt user again until he gets it right

die:
        mov     ah,4ch          ; terminate; errorlevel in al
        int     21h

start   endp
all     ends
        end     entry
