;--------------------------------------------------------------------------;
;  Program:    Ask    .Asm                                                 ;
;  Purpose:    Ask question in batch file and time out if desired.         ;
;  Notes:      Compiles under TURBO Assembler, v2.0. Should work on any    ;
;                 machine running MS-DOS, v2.xx or higher.                 ;
;  Status:     Released into the public domain. Enjoy! If you use it,      ;
;                 let me know what you think. You don't have to send       ;
;                 any money, just comments and suggestions.                ;
;  Updates:    23-Apr-90, v1.0, GAT                                        ;
;                 - initial version.                                       ;
;              13-May-90, GAT                                              ;
;                 - fixed problem with return code if time-out reached.    ;
;              08-Jul-90, GAT                                              ;
;                 - added macros to push/pop registers.                    ;
;              28-Aug-90, v1.1a, GAT                                       ;
;                 - put equates and macros in separate files.              ;
;                 - put common routines in libs.                           ;
;              12-Oct-91, v1.1b, GAT                                       ;
;                 - revised include file names.                            ;
;                 - replaced references to Push_M and Pop_M macros with    ;
;                    calls to push and pop.                                ;
;                 - removed local stack: it's not necessary.               ;
;--------------------------------------------------------------------------;

;--------------------------------------------------------------------------;
;  Author:     George A. Theall                                            ;
;  Phone:      +1 215 662 0558                                             ;
;  SnailMail:  TifaWARE                                                    ;
;              506 South 41st St., #3M                                     ;
;              Philadelphia, PA.  19104   USA                              ;
;  E-Mail:     theall@gdalsrv.sas.upenn.edu (Internet)                     ;
;--------------------------------------------------------------------------;

;--------------------------------------------------------------------------;
;                          D I R E C T I V E S                             ;
;--------------------------------------------------------------------------;
DOSSEG
MODEL                tiny

IDEAL
LOCALS
JUMPS

;
; This section comes from Misc.Inc.
;
@16BIT              EQU       (@CPU AND 8) EQ 0
@32BIT              EQU       (@CPU AND 8)
MACRO    ZERO     RegList                    ;; Zeros registers
   IRP      Reg, <RegList>
         xor      Reg, Reg
   ENDM
ENDM

;
; This section comes from DOS.Inc.
;
BELL                EQU       7
BS                  EQU       8
TAB                 EQU       9
CR                  EQU       13
LF                  EQU       10
ESCAPE              EQU       27             ; nb: ESC is a TASM keyword
SPACE               EQU       ' '
KEY_F1              EQU       3bh
KEY_F2              EQU       3ch
KEY_F3              EQU       3dh
KEY_F4              EQU       3eh
KEY_F5              EQU       3fh
KEY_F6              EQU       40h
KEY_F7              EQU       41h
KEY_F8              EQU       42h
KEY_F9              EQU       43h
KEY_F10             EQU       44h
KEY_HOME            EQU       47h
KEY_UP              EQU       48h
KEY_PGUP            EQU       49h
KEY_LEFT            EQU       4bh
KEY_RIGHT           EQU       4dh
KEY_END             EQU       4fh
KEY_DOWN            EQU       50h
KEY_PGDN            EQU       51h
KEY_INS             EQU       52h
KEY_DEL             EQU       53h
KEY_C_F1            EQU       5eh
KEY_C_F2            EQU       5fh
KEY_C_F3            EQU       60h
KEY_C_F4            EQU       61h
KEY_C_F5            EQU       62h
KEY_C_F6            EQU       63h
KEY_C_F7            EQU       64h
KEY_C_F8            EQU       65h
KEY_C_F9            EQU       66h
KEY_C_F10           EQU       67h
KEY_C_LEFT          EQU       73h
KEY_C_RIGHT         EQU       74h
KEY_C_END           EQU       75h
KEY_C_PGDN          EQU       76h
KEY_C_HOME          EQU       77h
KEY_C_PGUP          EQU       84h
KEY_F11             EQU       85h
KEY_F12             EQU       86h
KEY_C_F11           EQU       89h
KEY_C_F12           EQU       8ah
DOS                 EQU       21h            ; main MSDOS interrupt
STDIN               EQU       0              ; standard input
STDOUT              EQU       1              ; standard output
STDERR              EQU       2              ; error output
STDAUX              EQU       3              ; COM port
STDPRN              EQU       4              ; printer
TSRMAGIC            EQU       424bh          ; magic number
STRUC     ISR
          Entry     DW        10EBh          ; short jump ahead 16 bytes
          OldISR    DD        ?              ; next ISR in chain
          Sig       DW        TSRMAGIC       ; magic number
          EOIFlag   DB        ?              ; 0 (80) if soft(hard)ware int
          Reset     DW        ?              ; short jump to hardware reset
          Reserved  DB        7 dup (0)
ENDS
STRUC     ISRHOOK
          Vector    DB        ?              ; vector hooked
          Entry     DW        ?              ; offset of TSR entry point
ENDS
STRUC     TSRSIG
          Company   DB        8 dup (" ")    ; blank-padded company name
          Product   DB        8 dup (" ")    ; blank-padded product name
          Desc      DB        64 dup (0)     ; ASCIIZ product description
ENDS
GLOBAL at : PROC
GLOBAL errmsg : PROC
   GLOBAL ProgName : BYTE                    ; needed for errmsg()
   GLOBAL EOL : BYTE                         ; ditto
GLOBAL fgetc : PROC
GLOBAL fputc : PROC
GLOBAL fputs : PROC
GLOBAL getchar : PROC
GLOBAL getdate : PROC
GLOBAL getswtch : PROC
GLOBAL gettime : PROC
GLOBAL getvdos : PROC
GLOBAL getvect : PROC
GLOBAL isatty : PROC
GLOBAL kbhit : PROC
GLOBAL pause : PROC
GLOBAL putchar : PROC
GLOBAL setvect : PROC
GLOBAL sleep : PROC
GLOBAL find_NextISR : PROC
GLOBAL find_PrevISR : PROC
GLOBAL hook_ISR : PROC
GLOBAL unhook_ISR : PROC
GLOBAL free_Env : PROC
GLOBAL fake_Env : PROC
GLOBAL check_ifInstalled : PROC
GLOBAL install_TSR : PROC
GLOBAL remove_TSR : PROC

;
; This section comes from Math.Inc.
;
GLOBAL atoi : PROC
GLOBAL atou : PROC
GLOBAL utoa : PROC

;
; This section comes from String.Inc.
;
EOS                 EQU       0              ; terminates strings
GLOBAL isdigit : PROC
GLOBAL islower : PROC
GLOBAL isupper : PROC
GLOBAL iswhite : PROC
GLOBAL memcmp : PROC
GLOBAL strchr : PROC
GLOBAL strcmp : PROC
GLOBAL strlen : PROC
GLOBAL tolower : PROC
GLOBAL toupper : PROC


VERSION   equ       '1.1b'                   ; current version of program
ERRH      equ       255                      ; errorlevel if help given


;--------------------------------------------------------------------------;
;                        C O D E    S E G M E N T                          ;
;--------------------------------------------------------------------------;
CODESEG

ORG       80h                                ; commandline
LABEL     CmdLen    BYTE
          db        ?
LABEL     CmdLine   BYTE
          db        127 dup (?)

ORG       100h                               ; start of .COM file
STARTUPCODE
          jmp       main                     ; skip over data and stack

;--------------------------------------------------------------------------;
;                               D A T A                                    ;
;--------------------------------------------------------------------------;
LABEL     ProgName  BYTE
          db        'ask: ', EOS
LABEL     EOL       BYTE
          db        '.', CR, LF, EOS
LABEL     HelpMsg   BYTE
          db        CR, LF
          db        'TifaWARE ASK, v', VERSION, ', ', ??Date
          db        ' - ask questions in batch files.', CR, LF
          db        'Usage: ask [-options] [msgtxt]', CR, LF, LF
          db        'Options:', CR, LF
          db        '  -l  = convert response to lower case', CR, LF
          db        '  -tn = wait n seconds before timing out', CR, LF
          db        '  -u  = convert response to upper case', CR, LF
          db        '  -?  = display this help message', CR, LF, LF
          db        'msgtxt is an optional message to display.', CR, LF, EOS
LABEL     Err1Msg   BYTE
          db        'illegal option -- '
LABEL     OptCh     BYTE
          db        ?
          db        EOS
LABEL     Err2Msg   BYTE
          db        'time-out value not specified', EOS
LABEL     Err3Msg   BYTE
          db        'time-out value too large -- ', EOS

SwitCh    db        '-'                      ; char introducing options
HFlag     db        0                        ; flag for on-line help
LFlag     db        0                        ; flag for lowercase response
TFlag     db        0                        ; flag for time-out
UFlag     db        0                        ; flag for uppercase response
Delay     dw        ?                        ; time to pause for key
MsgLen    db        0                        ; length of message text
MsgTxt    dw        ?                        ; near pointer to message text
RCode     db        0                        ; program return code


;--------------------------------------------------------------------------;
;                           P R O C E D U R E S                            ;
;--------------------------------------------------------------------------;
;----  skip_Spaces  -------------------------------------------------------;
;  Purpose:    Skips past spaces in a string.                              ;
;  Notes:      Scanning stops with either a non-space *OR* CX = 0.         ;
;  Entry:      DS:SI = start of string to scan.                            ;
;  Exit:       AL = next non-space character,                              ;
;              CX is adjusted as necessary,                                ;
;              DS:SI = pointer to next non-space.                          ;
;  Calls:      none                                                        ;
;  Changes:    AL, CX, SI                                                  ;
;--------------------------------------------------------------------------;
PROC skip_Spaces

          jcxz      SHORT @@Fin
@@NextCh:
          lodsb
          cmp       al, ' '
          loopz     @@NextCh
          jz        SHORT @@Fin              ; CX = 0; don't adjust

          inc       cx                       ; adjust counters if cx > 0
          dec       si

@@Fin:
          ret
ENDP skip_Spaces


;----  get_Opt  -----------------------------------------------------------;
;  Purpose:    Get a commandline option.                                   ;
;  Notes:      none                                                        ;
;  Entry:      AL = option character,                                      ;
;              CX = count of characters left in commandline,               ;
;              DS:SI = pointer to first option to process.                 ;
;  Exit:       CX = count of characters left _after_ processing,           ;
;              DS:SI = pointer to whitespace _after_ options,              ;
;  Calls:      tolower, errmsg, isdigit, atou, fputs                       ;
;  Changes:    AX, BL, CX, DX, SI,                                         ;
;              [OptCh], [HFlag], [LFlag], [TFlag], [UFlag], [Delay]        ;
;--------------------------------------------------------------------------;
PROC get_Opt

          mov       [OptCh], al              ; save for later
          call      tolower                  ; use only lowercase in cmp.
          cmp       al, 'l'
          jz        SHORT @@OptL
          cmp       al, 't'
          jz        SHORT @@OptT
          cmp       al, 'u'
          jz        SHORT @@OptU
          cmp       al, '?'
          jz        SHORT @@OptH
          mov       dx, OFFSET Err1Msg       ; unrecognized option
          call      errmsg                   ; then *** DROP THRU *** to OptH

;
; Various possible options.
;
@@OptH:
          mov       [HFlag], 1               ; set help flag
          jmp       SHORT @@Fin

@@OptL:
          mov       [LFlag], 1               ; set lowercase flag
          jmp       SHORT @@Fin

@@OptT:
          mov       [TFlag], 1               ; set time-out flag
          mov       al, [BYTE PTR si]        ; get next character
          call      isdigit                  ; if not a digit, trouble!
          jz        SHORT @@GetDelay

          mov       dx, OFFSET Err2Msg       ; no delay specified
          call      errmsg
          jmp       @@OptH

@@GetDelay:
          mov       dx, si                   ; save to adjust CX and if error
          call      atou
          pushf                              ; preserve flags
          add       cx, dx                   ; adjust counter
          sub       cx, si
          popf                               ; restore flags
          jc        SHORT @@BadDelay         ; error in conversion?
          cmp       ax, 60*60*12             ; 12 or more hours?
          jae       SHORT @@BadDelay         ;   yes, bad delay
          mov       [Delay], ax
          jmp       SHORT @@Fin

@@BadDelay:
          push      dx
          mov       bx, STDERR
          mov       dx, OFFSET ProgName
          call      fputs
          mov       dx, OFFSET Err3Msg
          call      fputs
          pop       dx
          mov       al, [si]                 ; save next non-digit
          mov       [BYTE PTR si], EOS       ; replace with EOS
          call      fputs
          mov       [si], al                 ; restore it
          mov       dx, OFFSET EOL
          call      fputs
          jmp       SHORT @@OptH

@@OptU:
          mov       [UFlag], 1               ; set uppercase flag

@@Fin:
          ret
ENDP get_Opt


;----  get_Arg  -----------------------------------------------------------;
;  Purpose:    Gets a non-option from the set of commandline arguments.    ;
;  Notes:      Anything left on the commandline is user's message text.    ;
;  Entry:      CX = count of characters left in commandline,               ;
;              DS:SI = pointer to argument to process.                     ;
;  Exit:       CX = zero                                                   ;
;              DS:SI = points to CR after commandline.                     ;
;  Calls:      none                                                        ;
;  Changes:    CX, SI                                                      ;
;              [MsgLen], [MsgTxt]                                          ;
;--------------------------------------------------------------------------;
PROC get_Arg

          mov       [MsgLen], cl             ; for safekeeping
          mov       [MsgTxt], si
          add       si, cx                   ; adjust so nothing's left
          ZERO      cl
          mov       [BYTE PTR si], EOS       ; finish off string

          ret
ENDP get_Arg


;----  process_CmdLine  ---------------------------------------------------;
;  Purpose:    Processes commandline arguments.                            ;
;  Notes:      A switch character by itself is ignored.                    ;
;              No arguments whatsoever causes help flag to be set.         ;
;  Entry:      n/a                                                         ;
;  Exit:       n/a                                                         ;
;  Calls:      skip_Spaces, get_Opt, get_Arg                               ;
;  Changes:    AX, CX, SI,                                                 ;
;              BL, DX (get_Opt),                                           ;
;              [HFlag],                                                    ;
;              [OptCh], [LFlag], [TFlag], [UFlag], [Delay] (get_Opt),      ;
;              [MsgLen], [MsgTxt] (get_Arg),                               ;
;              Direction flag is cleared.                                  ;
;--------------------------------------------------------------------------;
PROC process_CmdLine

          cld                                ; forward, march!
          ZERO      ch, ch
          mov       cl, [CmdLen]             ; length of commandline
          mov       si, OFFSET CmdLine       ; offset to start of commandline

          call      skip_Spaces              ; check if any args supplied
          or        cl, cl
          jnz       SHORT @@ArgLoop
          mov       [HFlag], 1               ; if none, set help flag
          jmp       SHORT @@Fin

;
; For each blank-delineated argument on the commandline...
;
@@ArgLoop:
          lodsb                              ; next character
          dec       cl
          cmp       al, [SwitCh]             ; is it the switch character?
          jnz       SHORT @@NonOpt           ;   no

;
; Isolate each option and process it. Stop when a space is reached.
;
@@OptLoop:
          jcxz      SHORT @@Fin              ; abort if nothing left
          lodsb
          dec       cl
          cmp       al, ' '
          jz        SHORT @@NextArg          ; abort when space reached
          call      get_Opt
          jmp       @@OptLoop

;
; Process the current argument, which is *not* an option.
; Then, *drop thru* to advance to next argument.
;
@@NonOpt:
          dec       si                       ; back up one character
          inc       cl
          call      get_Arg

;
; Skip over spaces until next argument is reached.
;
@@NextArg:
          call      skip_Spaces
          or        cl, cl
          jnz       @@ArgLoop

@@Fin:
          ret
ENDP process_CmdLine


;--------------------------------------------------------------------------;
;                         E N T R Y   P O I N T                            ;
;--------------------------------------------------------------------------;
;----  main  --------------------------------------------------------------;
;  Purpose:    Main section of program.                                    ;
;  Notes:      none                                                        ;
;  Entry:      Arguments as desired                                        ;
;  Exit:       Return code as follows:                                     ;
;                   0   => program timed-out                               ;
;                   255 => on-line help requested                          ;
;                   else => ASCII value of character pressed.              ;
;  Calls:      process_CmdLine, fputs, pause, getchar, tolower, toupper    ;
;  Changes:    n/a                                                         ;
;--------------------------------------------------------------------------;
main:

;
; Process commandline arguments. If the variable HFlag is set, then
; on-line help is displayed and the program immediately terminates.
;
          call      process_CmdLine          ; process commandline args

          cmp       [HFlag], 0               ; is help needed?
          jz        SHORT @@NoHelp           ;   no
          mov       al, ERRH                 ;   yes, so set return code
          mov       bx, STDERR
          mov       dx, OFFSET HelpMsg       ;     point to help message
          call      fputs
          jmp       SHORT @@Fin              ;     and jump to end of program

;
; Display any message from commandline then get keypress from user.
;
@@NoHelp:
          mov       bx, STDOUT               ; everything to stdout
          cmp       [MsgLen], 0              ; anything to print out?
          jz        SHORT @@NoPrompt         ;   nope
          mov       dx, [MsgTxt]
          call      fputs

@@NoPrompt:
          cmp       [TFlag], 0               ; need to wait?
          jz        SHORT @@KeyIn            ;   no
          mov       ax, [Delay]              ;   yes, so...
          call      pause                    ;     pause
          jz        SHORT @@KeyIn            ;     zf means a key is ready
          ZERO      al                       ;     set return code to zero
          jmp       SHORT @@NewLine

@@KeyIn:
          call      getchar

;
; Convert character in AL as necessary. NB: if both '-l' and '-u' options
; are specified, the return value will be based on *uppercase* value.
;
          cmp       [LFlag], 0               ; convert to lowercase?
          jz        SHORT @@MaybeUpper       ;   no
          call      tolower

@@MaybeUpper:
          cmp       [UFlag], 0               ; convert to uppercase?
          jz        SHORT @@NewLine          ;   no
          call      toupper

;
; Add a final newline to keep things neat.
;
@@NewLine:
          mov       dx, OFFSET EOL + 1
          call      fputs

;
; Ok, let's terminate the program. Return code is already in AL.
;
@@Fin:
          mov       ah, 4ch
          int       DOS

EVEN
Buffer   db    ?                          ; space for single character
                                          ; nb: shared by fgetc() & fputc()


;-------------------------------------------------------------------------;
;  Purpose:    Reads a character from specified device.
;  Notes:      No checks are done on BX's validity.
;              Buffer is shared by fputc(). Do *NOT* use in a 
;                 multitasking environment like DESQview.
;  Requires:   8086-class CPU and DOS v2.0 or better.
;  Entry:      BX = device handle.
;  Exit:       AL = character,
;              Carry flag set on error (AX holds error code).
;  Calls:      none
;  Changes:    AX
;              flags
;-------------------------------------------------------------------------;
PROC fgetc

   push     cx dx
IF @DataSize NE 0
   push     ds
   mov      ax, @data
   mov      ds, ax
ENDIF

   mov      dx, OFFSET Buffer             ; point to storage
   mov      cx, 1                         ; only need 1 char
   mov      ah, 3fh
   int      DOS                           ; get it
   jc       SHORT @@Fin                   ; abort on error
   mov      al, [Buffer]

@@Fin:
IF @DataSize NE 0
   pop      ds
ENDIF
   pop      dx cx
   ret

ENDP fgetc


;-------------------------------------------------------------------------;
;  Purpose:    Writes a character to specified device.
;  Notes:      No checks are done on BX's validity.
;              Buffer is shared by fputc(). Do *NOT* use in a 
;                 multitasking environment like DESQview.
;  Requires:   8086-class CPU and DOS v2.0 or better.
;  Entry:      AL = character to display,
;              BX = device handle.
;  Exit:       AL = 1 if successful,
;              Carry flag set on error (AX holds error code).
;  Calls:      none
;  Changes:    AX
;-------------------------------------------------------------------------;
PROC fputc

   push     cx dx
IF @DataSize NE 0
   push     ds
   mov      dx, @data
   mov      ds, ax
ENDIF

   mov      dx, OFFSET Buffer             ; point to storage
   mov      [Buffer], al                  ; save char
   mov      cx, 1                         ; only write 1 char
   mov      ah, 40h
   int      DOS

IF @DataSize NE 0
   pop      ds
ENDIF
   pop      dx cx
   ret

ENDP fputc


;-------------------------------------------------------------------------;
;  Purpose:    Reads a character from STDIN.
;  Notes:      Character is echoed to display.
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      n/a
;  Exit:       AL = character.
;  Calls:      none
;  Changes:    AX
;-------------------------------------------------------------------------;
PROC getchar

   mov      ah, 1
   int      DOS
   ret

ENDP getchar


;-------------------------------------------------------------------------;
;  Purpose:    Writes a character to STDOUT device.
;  Notes:      none
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      DL = character to display.
;  Exit:       n/a
;  Calls:      none
;  Changes:    none
;-------------------------------------------------------------------------;
PROC putchar

   push     ax
   mov      ah, 2
   int      DOS
   pop      ax
   ret

ENDP putchar


;-------------------------------------------------------------------------;
;  Purpose:    Checks if a character is ready for input from STDIN.
;  Notes:      none
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      n/a
;  Exit:       zf = 1 if character available.
;  Calls:      none
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC kbhit

   push     ax
   mov      ah, 0bh
   int      DOS
   cmp      al, 0ffh                      ; AL = FFh if character ready
   pop      ax
   ret

ENDP kbhit


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Writes an ASCIIZ string to specified device.
;  Notes:      A zero-length string doesn't seem to cause problems when
;                 this output function is used.
;  Requires:   8086-class CPU and DOS v2.0 or better.
;  Entry:      BX = device handle,
;              DS:DX = pointer to string.
;  Exit:       Carry flag set if EOS wasn't found or handle is invalid.
;  Calls:      strlen
;  Changes:    none
;-------------------------------------------------------------------------;
PROC fputs

   push     ax cx di es
   mov      ax, ds
   mov      es, ax
   mov      di, dx
   call     strlen                        ; set CX = length of string
   jc       SHORT @@Fin                   ; abort if problem finding end
   mov      ah, 40h                       ; MS-DOS raw output function
   int      DOS
@@Fin:
   pop      es di cx ax
   ret

ENDP fputs


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Writes an error message to stderr.
;  Notes:      none
;  Requires:   8086-class CPU and DOS v2.0 or better.
;  Entry:      DS:DX = pointer to error message.
;  Exit:       n/a
;  Calls:      fputs
;  Changes:    none
;-------------------------------------------------------------------------;
PROC errmsg

   push     bx dx
   mov      bx, STDERR
   mov      dx, OFFSET ProgName           ; display program name
   call     fputs
   pop      dx                            ; recover calling parameters
   push     dx                            ; and save again to avoid change
   call     fputs                         ; display error message
   mov      dx, OFFSET EOL
   call     fputs
   pop      dx bx
   ret

ENDP errmsg


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Gets current system date, based on DOS's internal clock.
;  Notes:      none
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      n/a
;  Exit:       AL = day of week (0 = Sunday)
;              DL = day (1 to 31)
;              DH = month (1 to 12)
;              CX = year (1980 to 2099)
;  Calls:      none
;  Changes:    AX, CX, DX
;-------------------------------------------------------------------------;
PROC getdate

   mov      ah, 2ah                       ; MS-DOS get system date function
   int      DOS
   ret

ENDP getdate


;-------------------------------------------------------------------------;
;  Purpose:    Gets current system time, based on DOS's internal clock.
;  Notes:      none
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      n/a
;  Exit:       CL = minutes (0 - 59)
;              CH = hour (0 - 23)
;              DL = hundredths of seconds (0 - 99)
;              DH = seconds (0 - 59)
;  Calls:      none
;  Changes:    CX, DX
;-------------------------------------------------------------------------;
PROC gettime

   push     ax
   mov      ah, 2ch                       ; MS-DOS get system time function
   int      DOS
   pop      ax
   ret

ENDP gettime


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Pauses execution until a specified time.
;  Notes:      If time is 12 or more hours in advance, execution aborts.
;              Range for hours is 0-23, for minutes is 0-59, etc...
;              This is as accurate as the DOS clock.
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      AX:BX = time of day (hh.mm.ss.hs) at which to awaken.
;  Exit:       n/a
;  Calls:      gettime
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC at

   push     cx dx

@@TimeLoop:
   call     gettime                       ; get current time
   sub      cx, ax                        ; cx = current time - alarm time
   jz       SHORT @@CheckSecs             ; if zero, check seconds
   cmp      ah, 12                        ; adjust if alarm is a.m. ...
   jae      SHORT @@TooFar?
   cmp      ch, 0                         ;   and current time is p.m. ...
   jl       SHORT @@TooFar?               ;      (signed comparison!!!)
   sub      ch, 24                        ;   by subtracting 24 hours

@@TooFar?:
   neg      cx                            ; cx = alarm time - current time
   cmp      cx, 12 * 256                  ; more than 12 hours difference?
   ja       SHORT @@Fin                   ;   yep (unsigned comparison!!!)
   jmp      @@TimeLoop                    ;   nope (already checked if ==)

@@CheckSecs:
   cmp      dx, bx                        ; wait a few more seconds?
   jb       @@TimeLoop                    ;   yep
   or       dl, 1                         ;   no, clear zf

@@Fin:
   pop      dx cx
   ret

ENDP at


;-------------------------------------------------------------------------;
;  Purpose:    Pauses execution for a specified number of seconds or 
;                 until a keypress is detected.
;  Notes:      Delay should be less than 12 hours (43200 seconds) to 
;                 avoid checks on date rollover yet ensure we haven't
;                 paused too long. Delay is not checked however!
;              This procedure works by adding the delay to the current
;                 time and waiting until then. If the system clock is
;                 adjusted in meantime, results are unpredictable. I
;                 tried looping and calling gettime(), but that was
;                 inaccurate due to roundoff of hundreths of secs.
;  Requires:   8086-class CPU and DOS v2.0 or better.
;  Entry:      AX = delay time (in seconds).
;  Exit:       Zero flag set if input ready; cleared otherwise.
;  Calls:      gettime, kbhit
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC pause

   push     ax bx cx dx
   ZERO     bx
   mov      cx, 60                        ; 60 secs/min and 60 mins/hour
   ZERO     dx
   div      cx                            ; now ax = hours and minutes
   mov      bh, dl                        ; and bh = seconds (dh == 0)
   ZERO     dx
   div      cx                            ; now ax = hours, dx = minutes
   mov      ah, al                        ; hours
   mov      al, dl                        ; minutes

;
; At this point, AX:BX = amount of time to sleep (hh.mm.ss.hs).
;
   call     gettime                       ; get current time
   add      ax, cx                        ; compute alarm time
   add      bx, dx
   cmp      bh, 60                        ; too many seconds?
   jb       SHORT @@CheckMins             ;   nope
   sub      bh, 60                        ;   yep, adjust
   inc      al

@@CheckMins:
   cmp      al, 60                        ; too many minutes?
   jb       SHORT @@CheckHours            ;   nope
   sub      al, 60                        ;   yep, adjust
   inc      ah

@@CheckHours:
   cmp      ah, 24                        ; too many hours?
   jb       SHORT @@TimeLoop              ;   nope
   sub      ah, 24                        ;   yep, adjust

;
; Here's the main loop. Check for both keypress and alarm time.
; NB: Because of overhead in the code it's possible to overshoot
; the alarm time by a few hundreths of a second so check for that.
;
@@TimeLoop:
   call     kbhit                         ; check for user input
   jz       SHORT @@Fin                   ; and abort if present
   call     gettime                       ; get current time
   sub      cx, ax                        ; cx = current time - alarm time
   jz       SHORT @@CheckSecs             ; if zero, check seconds
   cmp      ah, 12                        ; adjust if alarm is a.m. ...
   jae      SHORT @@TooFar?
   cmp      ch, 0                         ;   and current time is p.m. ...
   jl       SHORT @@TooFar?               ;      (signed comparison!!!)
   sub      ch, 24                        ;   by subtracting 24 hours

@@TooFar?:
   neg      cx                            ; cx = alarm time - current time
   cmp      cx, 12 * 256                  ; more than 12 hours difference?
   ja       SHORT @@Fin                   ;   yep (unsigned comparison!!!)
   jmp      @@TimeLoop                    ;   nope (already checked if ==)

@@CheckSecs:
   cmp      dx, bx                        ; wait a few more seconds?
   jb       @@TimeLoop                    ;   yep
   or       dl, 1                         ;   no, clear zf

@@Fin:
   pop      dx cx bx ax
   ret

ENDP pause


;-------------------------------------------------------------------------;
;  Purpose:    Pauses execution for a specified number of seconds.
;  Notes:      Delay should be less than 12 hours (43200 seconds) due
;                 to the way at() is implemented. This is not checked!
;              This procedure works by adding the delay to the current
;                 time and waiting until then. If the system clock is
;                 adjusted in meantime, results are unpredictable. I
;                 tried looping and calling gettime(), but that was
;                 inaccurate due to roundoff of hundreths of secs.
;  Requires:   8086-class CPU and DOS v1.0 or better.
;  Entry:      AX = delay time (in seconds).
;  Exit:       n/a
;  Calls:      gettime, at
;  Changes:    none
;-------------------------------------------------------------------------;
PROC sleep

   push     ax bx cx dx
   ZERO     bx
   mov      cx, 60                        ; 60 secs/min and 60 mins/hour
   ZERO     dx
   div      cx                            ; now ax = hours and minutes
   mov      bh, dl                        ; and bh = seconds (dh == 0)
   ZERO     dx
   div      cx                            ; now ax = hours, dx = minutes
   mov      ah, al                        ; hours
   mov      al, dl                        ; minutes

;
; At this point, AX:BX = amount of time to sleep (hh.mm.ss.hs).
;
   call     gettime                       ; get current time
   add      ax, cx                        ; compute alarm time
   add      bx, dx
   cmp      bh, 60                        ; too many seconds?
   jb       SHORT @@CheckMins             ;   nope
   sub      bh, 60                        ;   yep, adjust
   inc      al

@@CheckMins:
   cmp      al, 60                        ; too many minutes?
   jb       SHORT @@CheckHours            ;   nope
   sub      al, 60                        ;   yep, adjust
   inc      ah

@@CheckHours:
   cmp      ah, 24                        ; too many hours?
   jb       SHORT @@Fin                   ;   nope
   sub      ah, 24                        ;   yep, adjust

@@Fin:
   call     at                            ; pause until alarm time
   pop      dx cx bx ax
   ret

ENDP sleep


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Converts string of digits to an *unsigned* integer in
;              range [0, 65535].
;  Notes:      Conversion stops with first non-numeric character.
;  Requires:   8086-class CPU.
;  Entry:      DS:SI = pointer to string of digits.
;  Exit:       AX = unsigned integer (garbage if cf = 1),
;              DS:SI = pointer to first non-digit found,
;              cf = 1 if number is too big.
;  Calls:      none
;  Changes:    AX, SI
;              flags
;-------------------------------------------------------------------------;
PROC atou

   push     bx cx dx                      ; DX destroyed by MUL below
   ZERO     ax                            ; AX = digit to convert
   ZERO     bx                            ; BX = integer word
   mov      cx, 10                        ; CX = conversion factor

@@NextCh:
   mov      bl, [si]                      ; get character
   cmp      bl, '0'                       ; test if a digit
   jb       SHORT @@Fin
   cmp      bl, '9'
   ja       SHORT @@Fin
   inc      si                            ; bump up pointer
   mul      cx                            ; multiply old result by 10
   jc       SHORT @@Overflow
   sub      bl, '0'                       ; convert digit
   add      ax, bx                        ; add current value
   jnc      @@NextCh                      ; continue unless result too big

@@Overflow:
   ZERO     cx                            ; denotes overflow
   jmp      @@NextCh

@@Fin:
   cmp      cx, 10                        ; cf = (cx != 10)
   pop      dx cx bx
   ret

ENDP atou


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Tests if character is a valid ASCII digit.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be tested.
;  Exit:       Zero flag set if true, cleared otherwise.
;  Calls:      none 
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC isdigit

   cmp      al, '0'                       ; if < '0' zf = 0
   jb       SHORT @@Fin
   cmp      al, '9'                       ; if > '9' zf = 0
   ja       SHORT @@Fin
   cmp      al, al                        ; set Z flag
@@Fin:
   ret

ENDP isdigit


;-------------------------------------------------------------------------;
;  Purpose:    Tests if character is lowercase.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be tested.
;  Exit:       Zero flag set if true, cleared otherwise.
;  Calls:      none 
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC islower

   cmp      al, 'a'                       ; if < 'a' zf = 0
   jb       SHORT @@Fin
   cmp      al, 'z'                       ; if > 'z' zf = 0
   ja       SHORT @@Fin
   cmp      al, al                        ; set Z flag
@@Fin:
   ret

ENDP islower


;-------------------------------------------------------------------------;
;  Purpose:    Tests if character is uppercase.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be tested.
;  Exit:       Zero flag set if true, cleared otherwise.
;  Calls:      none 
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC isupper

   cmp      al, 'A'                       ; if < 'A' zf = 0
   jb       SHORT @@Fin
   cmp      al, 'Z'                       ; if > 'Z' zf = 0
   ja       SHORT @@Fin
   cmp      al, al                        ; set Z flag
@@Fin:
   ret

ENDP isupper


;-------------------------------------------------------------------------;
;  Purpose:    Tests if character is an ASCII whitespace.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be tested.
;  Exit:       Zero flag set if true, cleared otherwise.
;  Calls:      none 
;  Changes:    flags
;-------------------------------------------------------------------------;
PROC iswhite

   cmp      al, SPACE                     ; if == SPACE then zf = 1
   jz       SHORT @@Fin
   cmp      al, TAB                       ; if == TAB then zf = 1
   jz       SHORT @@Fin
   cmp      al, LF                        ; if == LF then zf = 1
   jz       SHORT @@Fin
   cmp      al, CR                        ; if == CR then zf = 1
@@Fin:
   ret

ENDP iswhite


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Converts character to lowercase.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be converted.
;  Exit:       AL = converted character.
;  Calls:      none
;  Changes:    AL
;              flags
;-------------------------------------------------------------------------;
PROC tolower

   cmp      al, 'A'                       ; if < 'A' then done
   jb       SHORT @@Fin
   cmp      al, 'Z'                       ; if > 'Z' then done
   ja       SHORT @@Fin
   or       al, 20h                       ; make it lowercase
@@Fin:
   ret

ENDP tolower


;-------------------------------------------------------------------------;
;  Purpose:    Converts character to uppercase.
;  Notes:      none
;  Requires:   8086-class CPU.
;  Entry:      AL = character to be converted.
;  Exit:       AL = converted character.
;  Calls:      none
;  Changes:    AL
;              flags
;-------------------------------------------------------------------------;
PROC toupper

   cmp      al, 'a'                       ; if < 'a' then done
   jb       SHORT @@Fin
   cmp      al, 'z'                       ; if > 'z' then done
   ja       SHORT @@Fin
   and      al, not 20h                   ; make it lowercase
@@Fin:
   ret

ENDP toupper


EVEN
;-------------------------------------------------------------------------;
;  Purpose:    Calculates length of an ASCIIZ string.
;  Notes:      Terminal char is _not_ included in the count.
;  Requires:   8086-class CPU.
;  Entry:      ES:DI = pointer to string.
;  Exit:       CX = length of string,
;              cf = 0 and zf = 1 if EOS found,
;              cf = 1 and zf = 0 if EOS not found within segment.
;  Calls:      none
;  Changes:    CX,
;              flags
;-------------------------------------------------------------------------;
PROC strlen

   push     ax di
   pushf
   cld                                    ; scan forward only
   mov      al, EOS                       ; character to search for
   mov      cx, di                        ; where are we now
   not      cx                            ; what's left in segment - 1
   push     cx                            ; save char count
   repne    scasb
   je       SHORT @@Done
   scasb                                  ; test final char
   dec      cx                            ; avoids trouble with "not" below

@@Done:
   pop      ax                            ; get original count
   sub      cx, ax                        ; subtract current count
   not      cx                            ; and invert it
   popf                                   ; restore df
   dec      di
   cmp      [BYTE PTR es:di], EOS
   je       SHORT @@Fin                   ; cf = 0 if equal
   stc                                    ; set cf => error

@@Fin:
   pop      di ax
   ret

ENDP strlen


END
