; ============================================================================= ; CHOICE: set ERRORLEVEL according to a user's keypress from a list of choices ; ----------------------------------------------------------------------------- ; Version: 0.1.1 (fixed for 8086, 2021-09-05) ; 0.1 (first release, 2021-08-27) ; ============================================================================= ; Copyright 2021 S. V. Nickolas. ; ----------------------------------------------------------------------------- ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to ; deal with the Software without restriction, including without limitation the ; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ; sell copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; 1. Redistributions of source code must retain the above copyright notice, ; this list of conditions and the following disclaimers. ; 2. Redistributions in binary form must reproduce the above copyright ; notice, this list of conditions and the following disclaimers in the ; documentation and/or other materials provided with the distribution. ; 3. The name of the author may not be used to endorse or promote products ; derived from this Software without specific prior written permission. ; ----------------------------------------------------------------------------- ; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED ; WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ; MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ; ; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, ; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR ; OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ; ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ; ============================================================================= ; To assemble: nasm -f bin -o choice.com choice.a86 ; ============================================================================= ; ============================================================================= ; Syntax: CHOICE [/C[:]options] [/N] [/S] [/T[:]opt,seconds] [prompt] ; ----------------------------------------------------------------------------- ; /C Set allowed keys (default is YN) ; /N Do not print list of options and ? at end of line ; /S Case sensitive ; /T Set default option after timeout if no key pressed ; (maximum timeout is 99 seconds; option may not be outside the list) ; ============================================================================= org 0100h ; This program requires DOS 2.0 or later because it uses ERRORLEVEL. ; If some weirdo decides to try running us on MS-DOS 1.25 to see what ; would happen, just say "Incorrect DOS version" and exit via INT20 ; (because MS-DOS 1 does not support AH=4C). cpu 8086 entry: mov ah, 30h ; DOS 2 or later? int 21h cmp al, 02h ; major DOS version (0 on 1.x) jae okdos mov dx, edos1 ; no, print error and die screaming mov ah, 09h int 21h int 20h ; DOS 1 EXIT ; Case smash AH. Should be pretty obvious how this works. toupper: cmp ah, 'a' jb tou_ret ; can't touch this cmp ah, 'z' ja tou_ret ; can't touch this ; stop, hammertime and ah, 5Fh ; mask off the lowercase bit tou_ret: ret ; Process command line todone: jmp done ; optimization okdos: push cs ; make data segment == code segment pop ds mov si, 0081h ; start of command line top: cmp si, 0100h ; overrun jae todone mov ah, [si] ; get next character cmp ah, 0Dh ; CR - end of line jz todone cmp ah, ' ' ; kill whitespace jnz notspc inc si ; skip and continue jmp short top notspc: cmp ah, 09h ; a tab is fine too, as is a cat jnz nottab inc si ; skip and continue jmp short top nottab: cmp ah, '/' ; switch? jnz todone ; no, stop processing opttop: inc si ; next character mov ah, [si] call toupper ; smash case cmp ah, 'C' ; /C: select keys to allow jnz notc inc si ; next character cld ; zot option list mov di, opt ; (write 128 zeroes) xor al, al ; note: the empty switch case will be mov cx, 0080h ; caught later and crushed cs rep stosb ; not sure forcing ES=CS would be any ; more efficient than an override... mov di, opt ; reset pointer mov ah, [si] ; if followed immediately by :, skip : cmp ah, ':' jnz ctop inc si ; next character ctop: mov ah, [si] ; copy options to list cmp ah, 0Dh ; CR - end of line jz top cmp ah, '/' ; new switch - end of argument jz top cmp ah, ' ' ; space - end of argument jz top cmp ah, 09h ; tab - end of argument jz top mov [di], ah ; add it inc si ; advance pointers inc di jmp short ctop ; keep going notc: cmp ah, 'N' ; /N - don't display list of choices jnz notn mov byte [quiet], 1 ; set the flag inc si totop: jmp short top ; label is for a size optimization notn: cmp ah, 'T' ; /T - set default option and timeout jnz nott inc si ; next character mov ah, [si] cmp ah, ':' ; if followed immediately by :, skip : jnz notcolon inc si ; next character mov ah, [si] notcolon: cmp ah, 0Dh ; end of line - arguments incomplete jz synerr cmp ah, ' ' ; end of arg - arguments incomplete jz synerr cmp ah, 09h ; end of arg - arguments incomplete jz synerr mov [timeout], ah ; save default inc si mov ah, [si] ; next char ,? if not, error cmp ah, ',' jnz synerr inc si ; next character mov ah, [si] ; digit? if not, error cmp ah, '0' jb synerr cmp ah, '9' ja synerr and ah, 0Fh ; mask it off mov al, ah ; hold it inc si ; next character mov ah, [si] ; digit? if not, skip cmp ah, '0' jb onedigit cmp ah, '9' ja onedigit and ah, 0Fh ; mask it off mov bl, al ; the cheap LUT method of *10 xor bh, bh mov al, [ten+bx] add al, ah ; combine digits (limit of 99 is inc si ; documented by Microsoft) onedigit: mov [seconds], al ; save our timeout and loop back jmp short totop ; (pointer is already on next char) nott: cmp ah, 'S' ; /S: case-sensitive jnz badswtch ; no: invalid switch mov byte [nosmash], 1 ; set the flag inc si ; next character jmp short totop ; and loop back to continue badswtch: mov [fault], ah ; alter the message to include the mov dx, enoswitch ; letter of the invalid switch jmp short scream synerr: mov dx, esyn scream: mov ah, 09h ; die screaming (with error) int 21h mov ax, 4CFFh int 21h ; EXIT (code=255) ; Done with argument processing. Rest of command line is prompt. ; Now smash the case of options (if needed) and make sure that if a ; default option was set with /T that it is valid. done: mov ah, [opt] ; make sure we have any options set or ah, ah ; (someone didn't use /C by itself) jz synerr mov ah, [nosmash] ; skip the rest of the block if /S was or ah, ah ; specified jnz smashed mov di, opt ; upcase the entire option list optloop: mov ah, [di] ; get next potential option or ah, ah ; zero = end jz smashed call toupper ; upcase option, then keep going mov [di], ah inc di cmp di, optend ; end of our memory block? jb optloop ; no, keep going smashed: mov ah, [timeout] ; skip the rest of the block unless /T or ah, ah ; was specified jz toutok mov ah, [nosmash] ; if we're smashing case, smash the or ah, ah ; case of the default option too. jnz smashed2 mov ah, [timeout] ; smash case and then put it back. call toupper mov [timeout], ah smashed2: mov di, opt ; set initial pointer toutloop: mov ah, [di] ; get next option or ah, ah ; is it zero (end of list)? jz nerpski ; yes, error out cmp ah, [timeout] ; is it our char? jz toutok ; yes, so move on inc di ; next, please cmp di, optend ; end of list? jb toutloop ; no, keep going nerpski: mov dx, edefault ; OK, it's broken, so die screaming jmp short scream ; Display prompt. toutok: cmp si, 0100h ; end of memory? jae pmptdone ; yes, quit reading mov ah, [si] ; get our character cmp ah, 0Dh ; EOL? jz pmptdone ; yes, quit reading mov dl, ah ; tell DOS to write it mov ah, 02h int 21h inc si ; NEXT! jmp short toutok pmptdone: mov ah, [quiet] ; skip the rest of the block if /N was or ah, ah ; specified jnz shutup mov si, opt mov dl, '[' ; open prompt jmp short sepchar ; "[" the first time, "," thereafter... pmptloop: mov dl, ',' sepchar: mov ah, 02h int 21h pmptstrt: mov dl, [si] ; get character (DOS wants it in DL) or dl, dl ; zero (end of list)? jz pmptq ; done mov ah, 02h ; print it by DOS int 21h inc si ; next character mov ah, [si] or ah, ah ; zero (end of list)? jz pmptq ; yes, so end parsing cmp si, optend jb pmptloop pmptq: mov dx, closetag ; "]?" to close prompt mov ah, 09h int 21h shutup: mov ah, [timeout] ; if not using a timeout, use a simple or ah, ah ; keyboard read function. jz rdsimple mov ah, 2Ch ; get the initial system time int 21h ; (only seconds needed). mov [tank], dh tick: mov ah, 0Bh ; poll the keyboard via DOS int 21h or al, al ; is a key waiting? jnz rdsimple ; stop waiting and do normal read. mov ah, 2Ch ; no, get the new time int 21h cmp dh, [tank] ; tick? jz tick ; not yet, loop mov [tank], dh ; update mov ah, [seconds] ; count down dec ah mov [seconds], ah ; update or ah, ah jnz tick ; not done yet mov ah, [timeout] ; time's up, get default jmp short ckkey rdsimple: mov ah, 08h ; request blocking input from stdin int 21h mov ah, al ; move key to our work register mov al, [nosmash] ; are we smashing case? or al, al jnz ckkey ; no, skip call toupper ; do it, maggot ckkey: mov si, opt ; is the pressed key valid? mov al, 1 ; this will be our ERRORLEVEL ckkl: cmp ah, [si] ; check against next key in list jz gotit ; we gotcha! inc al ; increment ERRORLEVEL inc si ; increment offset mov bl, [si] ; zero? or bl, bl jz ckfail ; yes, so stop looking cmp si, optend ; end of the line jb ckkl ; nope, keep looking (otherwise error) ckfail: mov ah, 02h mov dl, 07h ; beep int 21h jmp short rdsimple ; even if we have a timeout, a key was ; pressed, so ignore the timeout. gotit: mov [tank], al ; hold this mov dl, ah ; echo the key mov ah, 02h int 21h mov dx, crlf ; write newline mov ah, 09h int 21h mov al, [tank] ; get our ERRORLEVEL back mov ah, 4Ch ; and send it back to DOS as we int 21h ; EXIT ten db 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 edos1 db "Incorrect DOS version", 0Dh, 0Ah, "$" esyn db "Syntax error", 0Dh, 0Ah, "$" enoswitch db "Invalid switch - /" fault db 00h crlf db 0Dh, 0Ah, "$" edefault db "Default option not valid", 0Dh, 0Ah, "$" closetag db "]?$" tank db 00h quiet db 00h nosmash db 00h timeout db 00h seconds db 00h opt db "YN", 0 optend equ opt+128