Category : Utilities for DOS and Windows Machines
Archive   : RECALL11.ZIP
Filename : RECALL.ASM

 
Output of file : RECALL.ASM contained in archive : RECALL11.ZIP
;--------------------------------------------------------------------------;
; Program: Recall .Asm ;
; Purpose: Commandline editor and history TSR. ;
; Notes: Compiles under TURBO Assembler, v2.0. Requires DOS v2.xx ;
; or higher. Editing keys are coded as PC extended scan ;
; codes; otherwise, this uses only DOS calls. ;
; The overall design is derived from RDE (aka, Rainbow DOS ;
; Editor) by Joe Kneidel. The methods used to install and ;
; uninstall this TSR are from _MS-DOS Developer's Guide_, ;
; by Angermayer and Jaeger. ;
; 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: 24-Oct-90, v1.0a, GAT ;
; - initial version ;
; 28-Oct-90, v1.0b, GAT ;
; - renamed get_LineFromUser to get_CmdLine and ;
; add_LineToBuffer to store_CmdInBuf. ;
; - made sure to zero out CH in add_LineToBuffer. ;
; - excluded CR from byte count in get_CmdLine. ;
; - kept track of CurCmd rather than PrevCmd/NextCmd and ;
; moved checks on command from recall_CmdFromBuf to ;
; mov_pcmd and mov_ncmd. ;
; - specified command table as an array of structures and ;
; revised ways it was accessed in get_CmdLine. ;
; - rearranged various procedures. ;
; - spruced up comments. ;
; 31-Oct-90, v1.1a, GAT ;
; - removed notices about preliminary notices. ;
; - cleanup up help message a bit. ;
; - avoided use of LABELs. ;
; - added list_CmdLines to list recall buffer contents. ;
;--------------------------------------------------------------------------;

;--------------------------------------------------------------------------;
; Author: George A. Theall ;
; Phone: +1 215 662 0558 ;
; SnailMail: TifaWARE ;
; 506 South 41st St., #3M ;
; Philadelphia, PA. 19104 USA ;
; E-Mail: [email protected] (Internet) ;
;--------------------------------------------------------------------------;

%NEWPAGE
;--------------------------------------------------------------------------;
; D I R E C T I V E S ;
;--------------------------------------------------------------------------;
DOSSEG
MODEL tiny

IDEAL
LOCALS
JUMPS

;
; This section comes from D:\ASM\INCLUDE\Equates.Inc.
;
EOS EQU 0 ; terminates strings
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

;
; This section comes from D:\ASM\INCLUDE\Macros.Inc.
;
MACRO Pop_M RegList ;; Pops registers off stack.
IRP Reg,
IFIDNI ,
popf
ELSE
pop Reg
ENDIF
ENDM
ENDM
MACRO Push_M RegList ;; Pushes registers onto stack.
IRP Reg,
IFIDNI ,
pushf
ELSE
push Reg
ENDIF

ENDM
ENDM
MACRO Zero Reg ;; Zeros any register.
xor Reg, Reg
ENDM


; BUFSIZE specifies size of the recall buffer for collecting commandlines.
; Values of 255 or below are risky because that's the maximum buffer size
; for subfunction 10 of Int 21h and my code in add_LineToBuffer does not
; make sure commandlines will fit. I foresee no problems, however, with
; larger values up to about 60K.
BUFSIZE equ 1024 ; >>>CHANGE AT YOUR RISK<<<
ENTRY equ OFFSET handle_Int21 ; entry point for my ISR
VERSION equ '1.1a' ; current version of RECALL
ERRH equ 1 ; errorlevel if help given
ERRINS equ 10 ; errorlevel if install failed
ERRNYI equ 20 ; errorlevel if not yet installed
OFF equ 0
ON equ 1


%NEWPAGE
;--------------------------------------------------------------------------;
; C O D E S E G M E N T ;
;--------------------------------------------------------------------------;
CODESEG

ORG 0 ; address of code segment start
SegStart DB ? ; used in install_TSR

ORG 2ch ; address of environment segment
EnvSeg DW ? ; used in install_TSR

ORG 80h ; address of commandline
CmdLen DB ?
CmdLine DB 127 DUP (?)

ORG 100h ; start of .COM file
STARTUPCODE
jmp main


%NEWPAGE
;--------------------------------------------------------------------------;
; R E S I D E N T D A T A ;
;--------------------------------------------------------------------------;
TSR_Sig DB 'TifaWARE RECALL v', VERSION, ' is now installed.'
DB CR, LF, EOS
OldInt21 DD ? ; address of old INT 21H handler
; MUST BE ofs 1st, then seg!
OldAX DW ? ; value of AX register when my
; handler is first called
OldStack DD ? ; address of caller's stack
CurCmd DW 0 ; pointer to current command
; in recall buffer
InsMode DB ON ; InsertMode toggle flag

STRUC CMD ; structure for editing cmd
Key DB ? ; extended code for key
Function DW ? ; address of editing function
ENDS
CmdTbl CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD
CMD <0, OFFSET ring_Bell> ; >>>must be last<<<


%NEWPAGE
;--------------------------------------------------------------------------;
; L O C A L S T A C K ;
;--------------------------------------------------------------------------;
DB 16 dup("STACK ") ; 128 bytes for local stack
StackTop = $


%NEWPAGE
;--------------------------------------------------------------------------;
; R E S I D E N T C O D E ;
;--------------------------------------------------------------------------;
;---- is_CharWhite ------------------------------------------------------;
; Purpose: Tests if character is either a blank or a tab. ;
; Notes: none ;
; Entry: AL = character to be tested. ;
; Exit: Zero flag set if true, cleared otherwise. ;
; Calls: none ;
; Changes: flags ;
;--------------------------------------------------------------------------;
PROC is_CharWhite

cmp al, SPACE ; if == SPACE then zf = 1
jz SHORT @@Fin
cmp al, TAB ; if == TAB then zf = 1
@@Fin:
ret
ENDP is_CharWhite


;---- get_KeyNoEcho -----------------------------------------------------;
; Purpose: Reads key from STDIN, waiting as necessary. ;
; Notes: Allows DESQview to operate efficiently if task inactive. ;
; Ctrl-C and Ctrl-Break generate Int 23h. ;
; Entry: n/a ;
; Exit: AL = character (0 => extended code available next). ;
; Calls: none ;
; Changes: AX ;
;--------------------------------------------------------------------------;
PROC get_KeyNoEcho

mov ah, 8
int DOS
ret
ENDP get_KeyNoEcho


;---- ring_Bell ---------------------------------------------------------;
; Purpose: Rings the console bell as a warning to user. ;
; Notes: none ;
; Entry: n/a ;
; Exit: n/a ;
; Calls: none ;
; Changes: none ;
;--------------------------------------------------------------------------;
PROC ring_Bell

push ax dx
mov ah, 2
mov dl, BELL
int DOS
pop dx ax
ret
ENDP ring_Bell


;---- display_Char ------------------------------------------------------;
; Purpose: Displays character on STDOUT and advances to next char. ;
; Notes: Do *not* call this procedure to display backspaces; use ;
; backup_Cursor instead. Though they'd be displayed ;
; properly, BX would be *incremented* here. ;
; Does *not* adjust CH or CL. ;
; Entry: AL = character to display, ;
; BX = pointer to current position in commandline. ;
; Exit: BX++ ;
; Calls: none ;
; Changes: BX ;
;--------------------------------------------------------------------------;
PROC display_Char

push ax dx
mov dl, al
mov ah, 2
int DOS
inc bx ; move to next char on cmdline
pop dx ax
ret
ENDP display_Char


;---- advance_Cursor ----------------------------------------------------;
; Purpose: Moves the cursor forwards on the screen. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; SI = # of bytes to advance. ;
; Exit: BX += SI, ;
; CH -= SI. ;
; Calls: display_Char ;
; Changes: AX, CH, ;
; BX (display_Char) ;
;--------------------------------------------------------------------------;
PROC advance_Cursor

or si, si ; anything to skip over?
jz SHORT @@Fin

; Adjust CH now. (This could be left until later - no big deal.)
mov al, ch
Zero ah
sub ax, si
mov ch, al ; CH -= SI

; Display SI characters on commandline.
push cx
mov cx, si
@@NextChar:
mov al, [bx]
call display_Char ; nb: increments BX too
loop SHORT @@NextChar
pop cx

@@Fin:
ret
ENDP advance_Cursor


;---- backup_Cursor -----------------------------------------------------;
; Purpose: Moves the cursor backwards on the screen. ;
; Notes: Does *not* handle properly line-wrapping yet. ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; SI = # of bytes to back up. ;
; Exit: BX -= SI, ;
; CH += SI. ;
; Calls: none ;
; Changes: AX, BX, CH ;
;--------------------------------------------------------------------------;
PROC backup_Cursor

or si, si ; anything to skip over?
jz SHORT @@Fin

; Adjust BX and CH now. (This could be left until later - no big deal.)
sub bx, si ; BX -= SI
mov al, ch
Zero ah
add ax, si
mov ch, al ; CH += SI

; Back up cursor by displaying non-destructive backspaces.
push cx dx
mov ah, 2
mov cx, si
mov dl, BS
@@PrevChar:
int DOS
loop SHORT @@PrevChar
pop dx cx

@@Fin:
ret
ENDP backup_Cursor


;---- delete_Chars ------------------------------------------------------;
; Purpose: Deletes characters from commandline. ;
; Notes: No checks are done on SI's validity; ie, caller should ;
; ensure there are enough characters on line to delete. ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline, ;
; SI = # of characters to delete. ;
; Exit: CH -= SI, ;
; CL += SI. ;
; Calls: display_Char, backup_Cursor ;
; Changes: CH, CL, SI, ;
; AX (backup_Cursor) ;
;--------------------------------------------------------------------------;
PROC delete_Chars

or si, si ; anything to delete?
jz SHORT @@Fin

; Adjust CH and CL now while I have SI handy. At the same time
; I am also computing the number of characters to shift.
mov al, cl
Zero ah
add ax, si
mov cl, al ; CL += SI
mov al, ch
sub ax, si
mov ch, al ; CH -= SI
push cx ; final values of CH and CL
push ax ; used to back up cursor

; Shift CH - SI characters remaining on line to the left by SI characters.
mov cx, ax ; CX = CH - SI
jcxz SHORT @@Coverup ; skip if deleting to eol
@@NextChar:
mov al, [bx+si]
mov [bx], al
call display_Char ; nb: increments BX too
loop SHORT @@NextChar

; Display spaces to overwrite chars remaining on line.
@@CoverUp:
mov al, SPACE
mov cx, si
@@NextBlank:
call display_Char ; nb: increments BX too
loop SHORT @@NextBlank

; Back up cursor to its location on invocation.
pop ax ; CH - SI
add si, ax ; SI += (CH - SI)
call backup_Cursor ; nb: decrements BX too
pop cx

; NB: BX will be incremented (CH - SI) + SI times by display_Char and
; decremented CH times by backup_Cursor so overall it won't change.
@@Fin:
ret
ENDP delete_Chars


;---- add_CharToLine ----------------------------------------------------;
; Purpose: Adds a character to commandline buffer. ;
; Notes: Checks to see if buffer would overflow first. ;
; Entry: AL = character to add, ;
; BX = pointer to current position in line, ;
; CH = number of characters until end of line, ;
; CL = number of bytes left in line. ;
; Exit: BX and CX changed as appropriate. ;
; Calls: display_Char, backup_Cursor, ring_Bell ;
; Changes: AX, BX, CX ;
;--------------------------------------------------------------------------;
PROC add_CharToLine

; Check for space unless insert mode is OFF *and* not at eol.
cmp [cs:InsMode], ON
je SHORT @@CheckForSpace
or ch, ch
jz SHORT @@CheckForSpace

; Overwrite existing character while in not at eol.
mov [bx], al
call display_Char ; nb: increments BX too
dec ch
jmp SHORT @@Fin

@@CheckForSpace:
or cl, cl
jz SHORT @@Abort
or ch, ch
jnz SHORT @@AddWithShift

; At end of line.
mov [bx], al
call display_Char ; nb: increments BX too
dec cl
jmp SHORT @@Fin

; Add character and shift everything to right over by 1 position.
@@AddWithShift:
push cx dx
mov cl, ch ; CH = chars to eol
Zero ch
mov si, cx ; save for backing up
inc cl ; but add 1 to display new char
@@NextChar:
mov dl, [bx] ; use DL as temporary storage
mov [bx], al
call display_Char ; nb: increments BX too
mov al, dl ; recover previous char
loop SHORT @@NextChar
call backup_Cursor ; nb: decrements BX too
pop dx cx
dec cl
jmp SHORT @@Fin

@@Abort:
call ring_Bell ; if out of space

@@Fin:
ret
ENDP add_CharToLine


;---- find_StartofPrevWord ----------------------------------------------;
; Purpose: Locates start of previous word in commandline. ;
; Notes: "Words" are delineated by blanks and/or start/finish of ;
; the commandline. ;
; Entry: BX = pointer to current position in commandline. ;
; Exit: SI = # of characters from BX to start of previous word. ;
; Calls: is_CharWhite ;
; Changes: AX, SI ;
;--------------------------------------------------------------------------;
PROC find_StartofPrevWord

push bx dx
inc dx
inc dx ; DX now points to bol
mov si, bx ; SI = current position

; Skip over any whitespace. Note: don't bother with 1st character -
; think of how it should behave if positioned at start of a word.
@@SkipWhite:
dec bx
cmp bx, dx
jb SHORT @@Fin ; done if BX < bol
mov al, [bx]
call is_CharWhite
jz SHORT @@SkipWhite

; Next skip over non-blanks until the start of the word.
@@SkipWord:
dec bx
cmp bx, dx
jb SHORT @@Fin ; done if BX < bol
mov al, [bx]
call is_CharWhite
jnz SHORT @@SkipWord

; Finally compute how many characters must be skipped.
@@Fin:
inc bx ; backed up 1 too many
sub si, bx
pop dx bx
ret
ENDP find_StartofPrevWord


;---- find_StartofNextWord ----------------------------------------------;
; Purpose: Locates start of next word in commandline. ;
; Notes: "Words" are delineated by blanks and/or start/finish of ;
; the commandline. ;
; Entry: BX = pointer to current position in commandline. ;
; Exit: SI = # of characters from BX to start of next word. ;
; Calls: is_CharWhite ;
; Changes: AX, SI ;
;--------------------------------------------------------------------------;
PROC find_StartofNextWord

push bx dx
mov dx, bx
mov al, ch
Zero ah
add dx, ax ; DX now points to eol

; Skip over any existing word. Note: unlike find_StartofPrevWord, here
; we do not want to initially skip ahead - imagine if cursor were at
; a blank before the start of a word.
@@SkipWord:
cmp bx, dx
je SHORT @@Fin ; done if BX = eol
mov al, [bx]
inc bx
call is_CharWhite
jnz SHORT @@SkipWord

; Next skip over whitespace until the start of the word.
@@SkipWhite:
cmp bx, dx
je SHORT @@Fin ; done if BX = eol
mov al, [bx]
inc bx
call is_CharWhite
jz SHORT @@SkipWhite
dec bx ; point back to white space

; Finally compute how many characters must be skipped.
@@Fin:
mov si, bx ; where we are now
pop dx bx
sub si, bx ; less where we started from
ret
ENDP find_StartofNextWord


;---- recall_CmdFromBuf -------------------------------------------------;
; Purpose: Replaces current with a commandline from buffer. ;
; Notes: Does *not* check SI's validity. ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline, ;
; SI = pointer to command in recall buffer. ;
; Entry: BX = pointer to end of new commandline, ;
; CH = 0, ;
; CL = # of bytes left in new commandline. ;
; Calls: del_line, display_Char, ring_Bell ;
; Changes: AL, BX, CH, CL, SI ;
;--------------------------------------------------------------------------;
PROC recall_CmdFromBuf

; Clear current commandline and display new one.
push si ; since del_line zaps SI
call del_line ; changes CH and CL such that CX
; == max # of chars in buffer
pop si
@@NextChar:
mov al, [cs:si]
cmp al, CR
je SHORT @@Fin
inc si
mov [bx], al
call display_Char ; nb: increments BX too
loop SHORT @@NextChar ; continue as long as CX > 0
cmp [BYTE cs:si], CR ; did loop end prematurely?
je SHORT @@Fin ; no
call ring_Bell ; yes, warn user

@@Fin:
ret
ENDP recall_CmdFromBuf


;---- mov_lchar ---------------------------------------------------------;
; Purpose: Moves cursor left in the commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX--, ;
; CH++, ;
; SI = 1 (or 0 if already at bol). ;
; Calls: backup_Cursor ;
; Changes: SI, ;
; BX, CH (backup_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_lchar

mov si, bx
sub si, dx
cmp si, 2
ja SHORT @@MoveIt ; at bol if BX - DX <= 2
Zero si
jmp SHORT @@Fin

@@MoveIt:
mov si, 1 ; move 1 character
call backup_Cursor ; nb: decrements BX too

@@Fin:
ret
ENDP mov_lchar


;---- mov_rchar ---------------------------------------------------------;
; Purpose: Moves cursor right in the commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX++, ;
; CH--, ;
; SI = 1 (or 0 if already at eol). ;
; Calls: advance_Cursor ;
; Changes: SI, ;
; AX, BX, CH (advance_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_rchar

Zero si ; set SI = 0 first
or ch, ch
jz SHORT @@Fin ; abort if CH = 0
inc si ; move 1 character
call advance_Cursor ; nb: increments BX

@@Fin:
ret
ENDP mov_rchar


;---- mov_lword ---------------------------------------------------------;
; Purpose: Moves cursor to start of previous word. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX and CH adjusted as appropriate. ;
; Calls: find_StartofPrevWord, backup_Cursor ;
; Changes: SI, (find_StartofPrevWord) ;
; AX, BX, CH (backup_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_lword

call find_StartofPrevWord
call backup_Cursor
ret
ENDP mov_lword


;---- mov_rword ---------------------------------------------------------;
; Purpose: Moves cursor to start of next word. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX and CH adjusted as appropriate. ;
; Calls: find_StartofNextWord, advance_Cursor ;
; Changes: SI, (find_StartofNextWord) ;
; AX, BX, CH (advance_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_rword

call find_StartofNextWord
call advance_Cursor
ret
ENDP mov_rword


;---- mov_bol -----------------------------------------------------------;
; Purpose: Moves cursor to start of commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX = DX + 2, ;
; CH = # of characters in commandline, ;
; SI = # of characters backed up. ;
; Calls: backup_Cursor ;
; Changes: SI, ;
; AX, BX, CH (backup_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_bol

mov si, bx
sub si, dx
dec si
dec si ; SI = BX - (DX + 2)
call backup_Cursor
ret
ENDP mov_bol


;---- mov_eol -----------------------------------------------------------;
; Purpose: Moves cursor to end of commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line. ;
; Exit: BX += CH, ;
; CH = 0, ;
; SI = # of characters advanced. ;
; Calls: advance_Cursor ;
; Changes: SI, ;
; AX, BX, CH (advance_Cursor) ;
;--------------------------------------------------------------------------;
PROC mov_eol

mov al, ch
Zero ah
mov si, ax ; SI = CH
call advance_Cursor
ret
ENDP mov_eol


;---- mov_pcmd ----------------------------------------------------------;
; Purpose: Replaces current with previous commandline from buffer. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Entry: BX = pointer to end of new commandline, ;
; CH = 0, ;
; CL = # of bytes left in new commandline, ;
; [CurCmd] adjusted. ;
; Calls: recall_CmdFromBuf, del_line ;
; Changes: CurCmd, ;
; AX, BX, CH, CL, SI, (recall_CmdFromBuf) ;
;--------------------------------------------------------------------------;
PROC mov_pcmd

push di

; Point DI to 2 bytes before CurCmd. Abort if this lies at or before
; start of recall buffer.
mov di, [cs:CurCmd]
dec di ; now at possible CR
dec di ; now at possible cmd's last char
cmp di, OFFSET RecallBuf
jbe SHORT @@Abort

; Scan backwards to start of buffer or until finding another CR.
push cx ; CH/CL for recall_CmdFromBuf
pushf
mov al, CR
mov cx, di
sub cx, OFFSET RecallBuf - 1
std ; scan backwards
repne scasb ; uses ES:DI
popf
pop cx
inc di ; should point to CR
cmp [BYTE es:di], CR
jne SHORT @@Abort

; Point SI to start of command and recall it.
inc di
mov si, di
mov [cs:CurCmd], si
call recall_CmdFromBuf
jmp SHORT @@Fin

; Nothing to recall, so point CurCmd to start of buffer
; and delete current line.
@@Abort:
mov [cs:CurCmd], OFFSET RecallBuf

call del_line

@@Fin:
pop di
ret
ENDP mov_pcmd


;---- mov_ncmd ----------------------------------------------------------;
; Purpose: Replaces current with next commandline from buffer. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Entry: BX = pointer to end of new commandline, ;
; CH = 0, ;
; CL = # of bytes left in new commandline, ;
; [CurCmd] adjusted. ;
; Calls: recall_CmdFromBuf, del_line ;
; Changes: [CurCmd], ;
; AX, BX, CH, CL, SI (recall_CmdFromBuf) ;
;--------------------------------------------------------------------------;
PROC mov_ncmd

push di

; Point DI to CurCmd. Abort if this lies at or after LastByte.
mov di, [cs:CurCmd]
cmp di, OFFSET LastByte
jae SHORT @@Abort

; Scan forwards to end of buffer or until finding another CR.
; NB: Scan stops before final CR in the recall buffer since
; there can never be a command after that; saves having to
; check DI against OFFSET LastByte.
push cx ; CH/CL for recall_CmdFromBuf
mov al, CR
mov cx, OFFSET LastByte ; *not* OFFSET LastByte + 1
sub cx, di
repne scasb ; uses ES:DI
pop cx
dec di ; should point to CR
cmp [BYTE es:di], CR
jne SHORT @@Abort

; Point SI to start of command and recall it.
inc di ; point to 1st char in next cmd
mov si, di
mov [cs:CurCmd], si
call recall_CmdFromBuf
jmp SHORT @@Fin

; Nothing to recall, so point CurCmd to just past end
; of recall buffer and delete current line.
@@Abort:
mov [cs:CurCmd], OFFSET LastByte + 1
call del_line

@@Fin:
pop di
ret
ENDP mov_ncmd


;---- del_lchar ---------------------------------------------------------;
; Purpose: Deletes character to left of cursor. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CL = # of bytes left in commandline. ;
; Exit: BX--, ;
; CL++. ;
; Calls: mov_lchar, delete_Chars ;
; Changes: BX, (mov_lchar), ;
; AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_lchar

call mov_lchar ; sets SI = 0 (at bol) or 1
call delete_Chars
ret
ENDP del_lchar


;---- del_rchar ---------------------------------------------------------;
; Purpose: Deletes character at cursor. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: BX--, ;
; CH--, ;
; CL++. ;
; Calls: delete_Chars ;
; Changes: AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_rchar

or ch, ch
jz SHORT @@Fin ; abort if already at eol
mov si, 1
call delete_Chars
@@Fin:
ret
ENDP del_rchar


;---- del_lword ---------------------------------------------------------;
; Purpose: Deletes word to left of cursor. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: BX, CH, and CL adjusted as appropriate. ;
; Calls: mov_lword, delete_Chars ;
; Changes: BX, (mov_lword), ;
; AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_lword

call mov_lword ; sets SI = 0 (at bol) or > 0
call delete_Chars
ret
ENDP del_lword


;---- del_rword ---------------------------------------------------------;
; Purpose: Deletes word to right of cursor. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: CH, and CL adjusted as appropriate. ;
; Calls: find_StartofNextWord, delete_Chars ;
; Changes: AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_rword

call find_StartofNextWord ; sets SI = 0 (at eol) or > 0
call delete_Chars
ret
ENDP del_rword


;---- del_bol -----------------------------------------------------------;
; Purpose: Deletes from cursor to start of commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: BX = DX + 2, ;
; CH = number of characters in commandline, ;
; CL -= BX - DX - 2. ;
; Calls: mov_bol, delete_Chars ;
; Changes: BX, (mov_bol) ;
; AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_bol

call mov_bol ; sets SI = 0 (at bol) or > 0
call delete_Chars
ret
ENDP del_bol


;---- del_eol -----------------------------------------------------------;
; Purpose: Deletes from cursor to end of commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: BX = DX + 2, ;
; CH = 0, ;
; CL -= CH. ;
; Calls: delete_Chars ;
; Changes: AX, CH, CL, SI (delete_Chars) ;
;--------------------------------------------------------------------------;
PROC del_eol

mov al, ch
Zero ah
mov si, ax
call delete_Chars
ret
ENDP del_eol


;---- del_line ----------------------------------------------------------;
; Purpose: Deletes entire commandline. ;
; Notes: none ;
; Entry: BX = pointer to current position in commandline, ;
; CH = # of bytes to end of line, ;
; CL = # of bytes left in commandline. ;
; Exit: BX = DX + 2, ;
; CH = 0, ;
; CL = [DX]. ;
; Calls: mov_bol, del_eol ;
; Changes: BX, (mov_bol) ;
; AX, CH, CL, SI (del_eol) ;
;--------------------------------------------------------------------------;
PROC del_line

call mov_bol
call del_eol
ret
ENDP del_line


;---- del_buf -----------------------------------------------------------;
; Purpose: Deletes all commands in recall buffer. ;
; Notes: Does not affect current commandline. ;
; This function is not documented elsewhere. ;
; Entry: n/a ;
; Exit: [CurCmd] = OFFSET LastByte + 1. ;
; Calls: init_Buf ;
; Changes: AX, [CurCmd] (init_Buf) ;
;--------------------------------------------------------------------------;
PROC del_buf

mov [WORD cs:CurCmd], 0
call init_Buf ; nb: changes CurCmd
ret
ENDP del_buf


;---- toggle_InsMode ----------------------------------------------------;
; Purpose: Toggles flag for insert mode. ;
; Notes: none ;
; Entry: n/a ;
; Exit: [InsMode] toggled. ;
; Calls: none ;
; Changes: [InsMode] ;
;--------------------------------------------------------------------------;
PROC toggle_InsMode

xor [cs:InsMode], 1
ret
ENDP toggle_InsMode


;---- init_Buf ----------------------------------------------------------;
; Purpose: Initializes recall buffer if necessary. ;
; Notes: Clears recall buffer if CurCmd is zero. Normally, CurCmd ;
; will take on values OFFSET RecallBuf and LastByte, ;
; or less. Zero should not otherwise occur. ;
; This is needed when scanning for previous commands - ;
; spurious CRs should not be encountered. ;
; Entry: [CurCmd] = pointer to current command in recall buffer. ;
; Exit: [CurCmd] = OFFSET LastByte + 1 if buffer is initialized. ;
; Calls: none ;
; Changes: AX, [CurCmd] possibly ;
;--------------------------------------------------------------------------;
PROC init_Buf

; Abort if [CurCmd] is non-zero.
cmp [WORD cs:CurCmd], 0
jne SHORT @@Fin

; Initialize buffer by zeroing out all but last byte. There put a CR
; so when searching backwards for commands I'll find at least one.
push cx di
Zero al ; fill with zeros
mov cx, BUFSIZE - 1
mov di, OFFSET RecallBuf
rep stosb ; uses ES:DI
mov [BYTE es:di], CR ; buffer ends with CR
pop di cx

; Point current command to past end of recall buffer. This is so both
; mov_pcmd and mov_ncmd will not find any commands yet still function
; without error (which would happen if [CurCmd] were left at 0).
mov [cs:CurCmd], OFFSET LastByte + 1

@@Fin:
ret
ENDP init_Buf


;---- get_CmdLine -------------------------------------------------------;
; Purpose: Reads a commandline from user. ;
; Notes: The caller's buffer is used as a scratch area to keep ;
; memory requirements to a minimum. ;
; Entry: DS:DX = buffer for storing commandline, ;
; [BYTE DS:DX] = maximum number of bytes to read. ;
; Exit: [BYTE DS:DX+1] = number of bytes actually read, ;
; [BYTE DS:DX+2] = 1st byte read from user. ;
; Calls: get_KeyNoEcho, add_CharToLine, [CmdTbl] ;
; Changes: AX, BX, CX, BP, [InsMode], [PrevCmd], [NextCmd] ;
;--------------------------------------------------------------------------;
PROC get_CmdLine

mov bx, dx ; BX used for indexed addressing
Zero ch ; bytes to end of line
mov cl, [bx] ; space left in buffer
dec cl ; less 1 for final CR
inc bx ; pointer to first spot in buffer
inc bx

; Get key and determine if it's an editing key or a regular character.
@@NewKey:
call get_KeyNoEcho ; get key from user
cmp al, CR ; is user done yet?
jz SHORT @@Fin
cmp al, BS ; BS is an editing key
je SHORT @@EditKey
cmp al, ESCAPE ; ESCAPE is another
je SHORT @@EditKey
or al, al ; was key zero?
jnz SHORT @@RegularChar ; no, then it's a regular char
call get_KeyNoEcho ; yes, get extended scan code

; Process extended key as an editing key. Invalid keys are not added
; to the commandline buffer; instead, they merely result in a bell.
@@EditKey:
mov bp, OFFSET CmdTbl ; point to table of editing cmds

@@NewCmd:
cmp [(CMD PTR cs:bp).Key], al
je SHORT @@ProcessCmd
add bp, SIZE CmdTbl
cmp [(CMD PTR cs:bp).Key], 0 ; zero marks end of table
jne SHORT @@NewCmd ; and must point to ring_Bell
; so execution drops thru!!!
@@ProcessCmd:
call [(CMD PTR cs:bp).Function]
jmp SHORT @@NewKey

; It's an ordinary character so add it to the commandline.
@@RegularChar:
call add_CharToLine
jmp SHORT @@NewKey

; Now determine number of bytes in buffer, put that count in [DX+1],
; and terminate buffer with a CR. NB: count excludes final CR.
@@Fin:
mov bx, dx ; point back to start of buffer
mov al, [bx] ; compute count
sub al, cl
dec al ; exclude final CR
Zero ah
mov [bx+1], al ; [DS:DX+1] = count
add bx, ax
mov [BYTE bx+2], CR ; place CR at end of buffer
ret
ENDP get_CmdLine


;---- store_CmdInBuf ----------------------------------------------------;
; Purpose: Stores the commandline at the end of the recall buffer. ;
; Notes: Commandlines consisting of 1 character (CR) are not saved. ;
; Entry: DS:DX = pointer to start of commandline, ;
; [BYTE DS:DX+1] = maximum number of bytes to read. ;
; Exit: [CurCmd] = LastByte + 1. ;
; Calls: none ;
; Changes: AX, BX, CX, DI, SI, [CurCmd] ;
;--------------------------------------------------------------------------;
PROC store_CmdInBuf

; Check length of commandline.
mov bx, dx
mov cl, [bx+1]
or cl, cl ; CL does not include final CR
jz SHORT @@Fin ; so if = 0, nothing's there
inc cl ; else set CX = # bytes in cmd
Zero ch ; *including* final CR

; Make room in recall buffer for commandline by shifting everything
; back by [DS:DX+1] characters.
push cx ds ; need both to copy to recall buf
mov ax, cs
mov ds, ax
mov di, OFFSET RecallBuf ; to start of buffer
mov si, di
add si, cx ; from start + CX
neg cx
add cx, BUFSIZE ; for BUFSIZE - [BYTE BX+1]
rep movsb ; move them
pop ds cx

; Add commandline in empty space at end of recall buffer. By this
; point DI will point to space for current commandline.
mov si, bx
inc si
inc si
rep movsb ; from DS:SI to ES:DI
mov [cs:CurCmd], OFFSET LastByte + 1

@@Fin:
ret
ENDP store_CmdInBuf


;---- handle_Int21 ------------------------------------------------------;
; Purpose: Passes calls to input strings along to my own handler. ;
; Notes: none ;
; Entry: AH = subfunction to perform ;
; Exit: If AH = 10, DS:DX points to buffer read from user. ;
; Calls: init_Buf, get_CmdLine, store_CmdInBuf ;
; Changes: n/a ;
;--------------------------------------------------------------------------;
PROC handle_Int21 FAR

; If the call is for buffered input, then use my handler;
; otherwise, pass it along to the old handler.
cmp ah, 10
jz SHORT @@SwitchStack
jmp [cs:OldInt21] ; old vector issues IRET

; Switch over to my own stack and save callers registers.
@@SwitchStack:
mov [cs:OldAX], ax ; can't push it on my stack yet
cli ; critical part - disallow INTs
mov [WORD cs:OldStack], sp
mov [WORD cs:OldStack+2], ss
mov ax, cs
mov ss, ax
mov sp, OFFSET StackTop
sti ; ok, out of critical section
push bx cx dx di si bp ds es
pushf

; Meat of my interrupt handler.
mov es, ax ; set ES = CX
cld
call init_Buf
call get_CmdLine
call store_CmdInBuf

; Restore caller's registers.
popf
pop es ds bp si di dx cx bx
cli
mov ss, [WORD cs:OldStack+2]
mov sp, [WORD cs:OldStack]
sti
mov ax, [cs:OldAX]

iret ; return to caller
ENDP handle_Int21


%NEWPAGE
;--------------------------------------------------------------------------;
; R E C A L L B U F F E R ;
;--------------------------------------------------------------------------;
RecallBuf = $ ; will overlay transient portion
LastByte = RecallBuf + BUFSIZE - 1 ; room for BUFSIZE characters
; and end of resident portion


%NEWPAGE
;--------------------------------------------------------------------------;
; T R A N S I E N T D A T A ;
;--------------------------------------------------------------------------;
ProgName DB 'recall: '
DB EOS
EOL DB '.', CR, LF
DB EOS
HelpMsg DB CR, LF
DB 'TifaWARE RECALL, v', VERSION, ', ', ??Date
DB ' - commandline editor and history TSR.', CR, LF
DB 'Usage: recall [-options]', CR, LF, LF
DB 'Options:', CR, LF
DB ' -i = install in memory', CR, LF
DB ' -l = list commandlines in recall buffer', CR, LF
DB ' -r = remove from memory', CR, LF
DB ' -? = display this help message', CR, LF, LF
DB 'Only one option can be specified at a time.', CR, LF
DB EOS
ErrMsgOpt DB 'illegal option -- '
OptCh DB ? ; room for offending character
DB EOS
ErrMsgRes DB 'already resident'
DB EOS
ErrMsgMem DB 'memory control blocks corrupt'
DB EOS
ErrMsgNYI DB 'not yet installed'
DB EOS
RemoveMsg DB 'successfully removed'
DB EOS

SwitCh DB '-' ; char introducing options
HFlag DB 0 ; flag for on-line help
IFlag DB 0 ; flag for installing TSR
LFlag DB 0 ; flag for listing commandlines
RFlag DB 0 ; flag for removing TSR


%NEWPAGE
;--------------------------------------------------------------------------;
; T R A N S I E N T C O D E ;
;--------------------------------------------------------------------------;
;---- check_ifInstalled -------------------------------------------------;
; Purpose: Checks if TSR has already been installed. ;
; Notes: Works by looking for TSR_Sig in segment owned by current ;
; interrupt handler. ;
; Entry: AL = interrupt to be tested. ;
; Exit: zf = 1 if installed; 0 otherwise. ;
; Calls: getvect, strcmp ;
; Changes: flags ;
;--------------------------------------------------------------------------;
PROC check_ifInstalled

push bx di si ds es
call getvect ; address of handler now in ES:BX
mov si, OFFSET TSR_Sig ; offset in DS to signature
mov di, si ; should be at same offset in ES
call strcmp ; do they match?
pop es ds si di bx
ret
ENDP check_ifInstalled


;---- install_TSR -------------------------------------------------------;
; Purpose: Installs TSR in memory after hooking into the interrupt ;
; specified by AL. ;
; Notes: Will not load if already present in memory. ;
; Does *NOT* return if installation is successful. ;
; Labels "SegStart" and "LastByte" must be defined for start ;
; and end of resident stuff respectively. ;
; "Entry" must be defined as the entry point to the ISR. ;
; Entry: AL = interrupt vector to hook, ;
; DS:DX = doubleword (DD) storage for old handler. ;
; Exit: AL = ERRINS if installation failed. ;
; Calls: check_ifInstalled, getvect, setvect, errmsg ;
; Changes: AL, DX, flags ;
;--------------------------------------------------------------------------;
PROC install_TSR

call check_ifInstalled
jnz SHORT @@FreeMemory ; proceed if not yet installed
mov dx, OFFSET ErrMsgRes ; else point to error message
jmp SHORT @@Fin ; and abort

; Free memory used by environment. NB: DOS will not make
; use of this memory block until the TSR is removed.
@@FreeMemory:
push ax es
mov es, [EnvSeg] ; point to the block
mov ah, 49h ; and let DOS free it
int DOS
pop es ax
jnc SHORT @@GrabVector ; proceed if no error occured
mov dx, OFFSET ErrMsgMem ; else point to error message
jmp SHORT @@Fin ; and abort

; NB: This is the point of no return - if the execution reaches this
; point it will result in the program going resident such that the
; procedure does *NOT* return.
;
; Now save old interrupt handler. NB: This must be in the *resident*
; data area since: (1) calls to int 21h not for buffered input will be
; passed along, and (2) it's needed to successfully uninstall the TSR.
@@GrabVector:
push es ; original ES needed in fputs()
call getvect ; returns handler addr in ES:BX
xchg dx, bx ; BX needed for index addressing
mov [bx], dx ; save offset followed by segment
mov [bx+2], es
pop es

; Now set it to my own handler.
mov dx, ENTRY
call setvect

; Let user know code's been installed and then go resident.
mov bx, 1 ; report goes to STDOUT
mov dx, OFFSET TSR_Sig
call fputs

; ****************************************************************************
; NB: TASM's IDEAL mode treats arguments of the OFFSET operator in a peculiar
; fashion, as can be seen by browsing the lexical grammer in Appendix A of
; the _Reference Guide_. If MASM mode were used, the expression below would
; be written "(OFFSET LastByte - OFFSET SegStart + 16) SHR 5". However, in
; IDEAL mode not only would "OFFSET SegStart + 16" be parsed as "OFFSET
; (SegStart + 16)" but also the result would be viewed as a relative quantity.
; As it is, TASM replaces labels below with their respective address values
; thereby computing the "correct" amount of memory to save.
; ****************************************************************************
; NB: While Angermayer and Jaeger in their book say 15 should be used
; below, I've found 16 is necessary to handle cases in which LastByte
; lies at the start of a paragraph. So what if I'm wasting an entire
; paragraph!
; ****************************************************************************
mov dx, (LastByte - SegStart + 16) SHR 4
mov ax, 3100h ; terminate/stay resident, rc = 0
int DOS ; via DOS

; This point is only reached on error.
@@Fin:
call errmsg
mov al, ERRINS
ret
ENDP install_TSR


;---- uninstall_TSR -----------------------------------------------------;
; Purpose: Uninstalls TSR. ;
; Notes: none ;
; Entry: AL = interrupt vector to restore, ;
; DX = offset to doubleword (DD) storage for old handler as ;
; saved by install_TSR. ;
; Exit: AL = ERRNYI if uninstallation failed. ;
; Calls: check_ifInstalled, errmsg, getvect, setvect ;
; Changes: AX (AH too), BX, DX, flags ;
;--------------------------------------------------------------------------;
PROC uninstall_TSR

call check_ifInstalled
jz SHORT @@FindTSRSeg ; proceed if installed
mov al, ERRNYI ; else flag error
mov dx, OFFSET ErrMsgNYI ; and point to error message
jmp SHORT @@Fin

; Locate segment holding TSR.
@@FindTSRSeg:
push es ; needed by errmsg() below
call getvect ; returns in ES:BX

; Restore previous interrupt handler stored in *RESIDENT* data segment.
push ds ; needed by errmsg() below
mov bx, dx ; ES:BX points to storage area
mov dx, [es:bx]
mov ds, [es:bx+2]
call setvect ; DS:DX = address of old handler
pop ds

; Release memory occupied by TSR. NB: ES already holds segment
; address of resident data and code based on calling getvect().
mov ah, 49h
int DOS
pop es
jnc SHORT @@FlagNoError ; continue if no error
mov al, ERRNYI ; else flag error
mov dx, OFFSET ErrMsgMem ; and point to error message
jmp SHORT @@Fin

@@FlagNoError:
Zero al ; indicate no error
mov dx, OFFSET RemoveMsg

@@Fin:
call errmsg ; display message
ret
ENDP uninstall_TSR


;---- list_CmdLines -----------------------------------------------------;
; Purpose: Lists commandlines in recall buffer. ;
; Notes: none ;
; Entry: AL = interrupt vector in use. ;
; Exit: AL = 0 if successful; ERRNYI otherwise. ;
; Calls: check_ifInstalled, getvect, errmsg ;
; Changes: AL, BX, CX, DX, DI ;
;--------------------------------------------------------------------------;
PROC list_CmdLines

call check_ifInstalled
jnz SHORT @@Abort ; not installed

; Locate segment controlled by resident code. Note I already
; know my ISR has been installed.
push ds es
call getvect ; returns ES:BX
mov bx, es
mov ds, bx

; Point to start of 1st complete command in recall buffer. NB:
; There will always be at least one command - "recall -l".
mov al, CR
mov cx, BUFSIZE
mov di, OFFSET RecallBuf
repne scasb ; uses ES:DI

; Display rest of buffer.
mov ah, 2 ; DOS subfunction to display char
@@NextChar:
mov dl, [di] ; get char
inc di
int DOS ; display it
cmp dl, CR ; need to display CR/LF?
loopne SHORT @@NextChar ; always decrements CX
mov dl, LF ; display LF now
int DOS
or cx, cx ; done yet?
jnz SHORT @@NextChar

pop es ds
Zero al ; flag no error
jmp SHORT @@Fin

@@Abort:
mov dx, OFFSET ErrMsgNYI
call errmsg
mov al, ERRNYI

@@Fin:
ret
ENDP list_CmdLines


;---- 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, ;
; Exit: n/a ;
; Calls: tolower, errmsg ;
; Changes: AX, DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag] ;
;--------------------------------------------------------------------------;
PROC get_Opt

mov [OptCh], al ; save for later
call tolower ; use only lowercase in cmp.
cmp al, 'i'
jz SHORT @@OptI
cmp al, 'l'
jz SHORT @@OptL
cmp al, 'r'
jz SHORT @@OptR
cmp al, '?'
jz SHORT @@OptH
mov dx, OFFSET ErrMsgOpt ; unrecognized option
call errmsg ; then *** DROP THRU *** to OptH

; Various possible options.
@@OptH:
mov [HFlag], ON ; set help flag
jmp SHORT @@Fin

@@OptI:
mov [IFlag], ON ; install in memory
jmp SHORT @@Fin

@@OptL:
mov [LFlag], ON ; list cmds in recall buffer
jmp SHORT @@Fin

@@OptR:
mov [RFlag], ON ; remove from memory

@@Fin:
ret
ENDP get_Opt


;---- process_CmdLine ---------------------------------------------------;
; Purpose: Processes commandline arguments. ;
; Notes: A switch character by itself is ignored. ;
; Entry: n/a ;
; Exit: n/a ;
; Calls: skip_Spaces, get_Opt ;
; Changes: AX, CX, SI, ;
; DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag] (get_Opt) ;
; 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], ON ; assume user needs help
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

; Any argument which is *not* an option is invalid. Set help flag and abort.
@@NonOpt:
mov [HFlag], ON
jmp SHORT @@Fin

; Skip over spaces until next argument is reached.
@@NextArg:
call skip_Spaces
or cl, cl
jnz @@ArgLoop

@@Fin:
ret
ENDP process_CmdLine


;---- main --------------------------------------------------------------;
; Purpose: Main section of program. ;
; Notes: none ;
; Entry: Arguments as desired ;
; Exit: Return code as follows: ;
; 0 => program ran successfully, ;
; ERRH => on-line help supplied, ;
; ERRINS => program could not be installed, ;
; ERRNYI => program was not yet installed. ;
; Calls: process_CmdLine, fputs, list_CmdLines, install_TSR, ;
; uninstall_TSR ;
; Changes: n/a ;
;--------------------------------------------------------------------------;
main:

; Process commandline arguments.
call process_CmdLine ; process commandline args
mov al, 21h ; vector to hook
mov dx, OFFSET OldInt21 ; storage for old intr handler
cmp [IFlag], ON ; install it?
je SHORT @@Install
cmp [LFlag], ON ; list commands?
je SHORT @@List
cmp [RFlag], ON ; remove it?
je SHORT @@Remove
mov bx, STDERR ; user must need help
mov dx, OFFSET HelpMsg
call fputs
mov al, ERRH
jmp SHORT @@Fin

@@List:
call list_CmdLines
jmp SHORT @@Fin

@@Install:
call install_TSR
jmp SHORT @@Fin

@@Remove:
call uninstall_TSR

; Terminate the program using as return code what's in AL.
@@Fin:
mov ah, 4ch
int DOS
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

IF @DataSize EQ 0
Push_M
ELSE
Push_M
mov ax, ds
mov es, ax
ENDIF
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:
IF @DataSize EQ 0
Pop_M
ELSE
Pop_M
ENDIF
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_M
push dx ; save again calling parameters
mov bx, STDERR
mov dx, OFFSET ProgName ; display program name
call fputs
pop dx ; recover calling parameters
call fputs ; display error message
mov dx, OFFSET EOL
call fputs
Pop_M
ret

ENDP errmsg


EVEN
;--------------------------------------------------------------------------;
; Purpose: Gets address of an interrupt handler.
; Notes: none
; Requires: 8086-class CPU and DOS v2.0 or better.
; Entry: AL = interrupt of interest.
; Exit: ES:BX = address of current interrupt handler
; Calls: none
; Changes: ES:BX
;--------------------------------------------------------------------------;
PROC getvect

push ax
mov ah, 35h ; find address of handler
int DOS ; returned in ES:BX
pop ax
ret
ENDP getvect


;--------------------------------------------------------------------------;
; Purpose: Sets an interrupt vector.
; Notes: none
; Requires: 8086-class CPU and DOS v1.0 or better.
; Entry: AL = interrupt of interest,
; DS:DX = address of new interrupt handler
; Exit: n/a
; Calls: none
; Changes: none
;--------------------------------------------------------------------------;
PROC setvect

push ax
mov ah, 25h ; set address of handler
int DOS
pop ax
ret
ENDP setvect


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


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_M
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_M
ret

ENDP strlen


EVEN
;--------------------------------------------------------------------------;
; Purpose: Compares two ASCIIZ strings.
; Notes: none
; Requires: 8086-class CPU.
; Entry: DS:SI = start of 1st string,
; ES:DI = start of 2nd string.
; Exit: zf = 1 if equal.
; cf = 1 if EOS not found within segment.
; Calls: strlen
; Changes: flags
;--------------------------------------------------------------------------;
PROC strcmp

Push_M
call strlen ; get length on 1 of the strings
jc SHORT @@Fin ; error
inc cx ; account for EOS

; There will always be at least one char in each string
; to compare - the terminal null.
pushf ; save direction flag
cld
repe cmpsb ; compare both strings
popf ; recover direction flag
dec di
dec si
cmpsb ; set flags based on final char

@@Fin:
Pop_M
ret
ENDP strcmp


END


  3 Responses to “Category : Utilities for DOS and Windows Machines
Archive   : RECALL11.ZIP
Filename : RECALL.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/