home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
pcmagazi
/
1990
/
16
/
cmdmacro.asm
< prev
next >
Wrap
Assembly Source File
|
1990-06-24
|
43KB
|
1,529 lines
; CMDMACRO.ASM
; (c) 1989, 1990 Ashok P. Nadkarni
;
; Module implementing macro and symbol feature for CMDEDIT.
;
; Symbols :
; CMDEDIT symbols can be defined either from the command line or read
; from a file during initialization. The syntax is given by
; defs symbolname expansion
; When the defined symbol appears as the first word in the line, it is
; replaced by its expansion. The rest of the line is unchanged. The
; following line defines a symbol called 'ed' that runs my editor.
;
; defs ed c:\util\editor
;
; Now if you type
;
; ed newfile
;
; the command
;
; c:\util\editor newfile
;
; will be executed.
;
; Symbols are expanded recursively.
;
; Macros :
;
; CMDEDIT macros can be defined either from the command line or read
; from a macro file when CMDEDIT is installed. In both cases, macros
; are defined using the same syntax. Macros may expand into multiple
; lines. In the latter case, each line of the expansion in passed to
; the calling application one at a time.
;
; Macros are defined using the CMDEDIT command 'defm' followed by the
; macro name. The macro name is separated from the 'defm' keyword by one
; or more spaces/tabs. Any characters after the name of the macro are ignored.
; Each line of the macro expansion is defined on a separate line. The
; expansion may contain any number of lines (limited by buffer space)
; and is terminated by a line that begins with the keyword 'endm'. For
; example, the following lines define a macro that will change the
; current directory from any disk:
; defm gotc
; c:
; cd \TURBOC
; endm
; Macro keywords are case-insensitive.
;
; Macro Parameters:
; Similar to batch files, macros can be passed parameters. (Read
; your DOS manual to find out about parameters). Although the
; concept is similar to DOS batch files, CMDEDIT parameters behave a
; little differently. Upto 9 parameters can be defined. These are
; indicated in macro definitions as '%n' where n is a digit from 1 to 9.
; A parameter can appear anywhere in the definition and need
; not be surrounded by whitespace. Also, the character % itself can be
; placed anywhere in the definition as long as it is not followed by a
; digit. If you do want a '%n' sequence in the expansion, indicate the '%'
; character as '%%'.
; For example, consider
; defm bf
; copy %1 a:\%1\%%1
; endm
; Then, when you type
; "bf myfile"
; the macro will expand to
; "copy myfile a:\myfile\%1"
; Note how %1 has been replaced by 'myfile' in two places but not the third.
;
; A macro cannot call another macro except if the call is the last
; line in the macro. Macros anywhere else are not expanded and the line
; is passed to the calling application without modification.
;
; Note that the macro name can be null as well. In this case, hitting
; carraige return on an blank line will result in that macro being run.
INCLUDE common.inc
INCLUDE general.inc
INCLUDE ascii.inc
INCLUDE dos.inc
PUBLIC macro_init
PUBLIC symbol_init
PUBLIC execute_defm
PUBLIC execute_defs
PUBLIC execute_delm
PUBLIC execute_dels
PUBLIC execute_rstsym
PUBLIC execute_rstmac
PUBLIC execute_cmdstat
PUBLIC get_macro_line
PUBLIC expand_macro
PUBLIC expand_symbol
PUBLIC expand_var
PUBLIC get_symbol
PUBLIC mac_stk
PUBLIC sym_stk
PUBLIC ismacsym
PLACEHOLDER equ PERCENT ;Placeholder character
INCLUDE buffers.inc
CSEG SEGMENT PARA PUBLIC 'CODE'
DGROUP GROUP CSEG
EXTRN endm_cmd:BYTE
EXTRN defm:BYTE
EXTRN defs:BYTE
EXTRN get_kbd_line:ABS
EXTRN source:WORD
EXTRN macro_level:WORD
EXTRN macro_ignore_char:BYTE
EXTRN lastchar:WORD
EXTRN linebuf:BYTE
EXTRN dot:WORD
EXTRN LINEBUF_END:ABS
EXTRN cur_macro:BYTE
EXTRN cur_macro_len:WORD
mac_stk $string_stack <> ;Descriptor for macro buffer
sym_stk $string_stack <> ;Descriptor for sym buffer
separator db 13 ;Separator string between macros
sep_len equ $-separator ;Length of separator string
macro_noroom_msg db 'Table full. Definition ignored.',CR,LF,DOLLAR
macro_prompt db CR,LF,'DEFM>',DOLLAR
EXTRN push_word:PROC
EXTRN push_string:PROC
EXTRN get_next_line:PROC
EXTRN reset_line:PROC
EXTRN abort_processing:PROC
EXTRN getargs:PROC
EXTRN stre_cmp:PROC
EXTRN set_disp_marks:PROC
EXTRN isspace:PROC
EXTRN isdelim:PROC
EXTRN skip_whitespace:PROC
EXTRN skip_nonwhite:PROC
EXTRN skip_nondelim:PROC
EXTRN makeroom:PROC
EXTRN remove_chars:PROC
EXTRN insert_chars:PROC
EXTRN output_counted_string:PROC
EXTRN output_newline:PROC
EXTRN locate_dosenv:PROC
ASSUME CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP
;+
; FUNCTION : macro_init,symbol_init
;
; Initializes the various data structures associated with the
; macro /symbol buffer. CALLER MUST ENSURE PASSED ADDRESSES ARE VALID
; AND BUFFER IS LARGE ENOUGH.
;
; Parameters:
; AX - length of buffer in bytes
; BX - address of buffer
;
; Returns:
; Nothing.
; Registers destroyed :
; BX
;-
macro_init proc near
push bx
mov bx,offset DGROUP:mac_stk ;bx := address of buffer descriptors
jmp short @macsym_init
symbol_init LABEL near
push bx
mov bx,offset DGROUP:sym_stk ;bx := address of buffer descriptors
@macsym_init:
xchg ax,cx ;CX = buffer size
pop ax ;AX = Buffer address
;BX points to appropriate descriptor
call near ptr strstk_init ;Initialize buffer and descriptor
;Store a separator into the macro buffer
call near ptr separate
ret
macro_init endp
;+
; FUNCTION : execute_rstmac, execute_rstsym
;
; Resets the various data structures associated with the
; macro /symbol buffer.
;
; Parameters:
; None.
;
; Returns:
; Nothing.
;
; Registers destroyed :
; AX,CX,BX,DX
;-
rst_macsym proc near
execute_rstmac LABEL near
; mov macro_level,0
mov bx,offset DGROUP:mac_stk ;bx := address of buffer descriptors
jmp short @rst_macsym
execute_rstsym LABEL near
mov bx,offset DGROUP:sym_stk ;bx := address of buffer descriptors
@rst_macsym:
call near ptr strstk_reset ;Re-initialize buffer and descriptor
;Store a separator into the macro buffer
call near ptr separate
ret
rst_macsym endp
;+
; FUNCTION : ismacsym
;
; Called to check if the passed string is a valid symbol or macro
; name. If found, the symbol/macro string stack pointer is set to the
; first line of the expansion of the symbol/macro.
;
; Parameters:
; AX - length of symbol name to be checked
; BX - address of the string stack descriptor (symbol or macro)
; SI - pointer to the symbol to be checked.
;
; Returns:
; CF - 1 if symbol not found, the string stack current pointer
; is undefined
; 0 if symbol found. The string stack pointer points to the
; first line of the expansion of the found symbol/macro.
; Register(s) destroyed:
; AX,CX,DX
;-
ismacsym proc near
@save si,di
mov di,ax ;DI<-length of symbol
call near ptr strstk_settop ;Reset macro stack pointer
@ismacsym_20:
; Loop start, DI = num chars in word, SI->start of word
mov ax,si ;AX->word
mov cx,di ;CX<-length of word
call near ptr strstk_bck_find ;Look backward for string
; Params AX,BX,CX
; BX unchanged
jc @ismacsym_98 ;Not found so return with CF set
; The name matched. Now make sure it is a macro name by ensuring
; previous string is a separator.
xor cx,cx ;cx<-length of pattern
call near ptr strstk_bck_match ;Set current to previous string
; BX unchanged
jc @ismacsym_98 ;start of buffer so return
; with CF set
push bx
call near ptr check_separator
pop bx
je @ismacsym_50 ;This is the one
; Wasn't a separator. Move back over it to start the hunt again.
xor cx,cx
call near ptr strstk_fwd_match
jmp short @ismacsym_20
@ismacsym_50:
; The macro/symbol has been found.
xor cx,cx ;CX<-match length
call near ptr strstk_fwd_match ;Skip over separator
xor cx,cx ;CX<-match length
call near ptr strstk_fwd_match ;Skip over macro/symbol name
clc ;Clear return flag
@ismacsym_98:
@restore
ret
ismacsym endp
;+
; FUNCTION : execute_dels, execute_delm
;
; execute_dels and execute_delm respectively delete symbols and
; macros from the appropriate stack. All symbols/macros listed on the
; line are deleted. If a particular symbol/macro is not defined, no
; error is generated.
;
; Parameters:
; SI -> first char in linebuf following this command
; CX == remaining num chars in the line
;
; Returns:
; Nothing.
;
; Register(s) destroyed:
; AX,BX,CX,DX
del_macsym proc near
execute_dels LABEL near
mov bx,offset DGROUP:sym_stk
jmp short @del_macsym_5
execute_delm LABEL near
cmp macro_level,1
jne @execute_delm_1
mov ax,E_NESTED_DELM
jmp near ptr abort_processing
@execute_delm_1:
mov bx,offset DGROUP:mac_stk
@del_macsym_5:
@save si,di
push bp
mov bp,sp
sub sp,2
num_remain equ <word ptr [bp-2]>
;
@del_macsym_10:
; At this point, SI->remaining chars in line, CX is number of chars
jcxz @del_macsym_99 ;No more chars in line
; Find first word
push bx ;Save buffer descriptor addr
call near ptr skip_whitespace ;SI->first non blank
mov di,si ;DI->start of word
call near ptr skip_nondelim ;SI->char after word
mov ax,si ;
xchg si,di ;SI->word, DI->rest of line
sub ax,si ;AX<-num chars in word
pop bx ;BX->buffer descriptor
push cx ;Save remaining count
call near ptr ismacsym ;Is it in macro/symbol stack ?
pop cx ;Restore remaining count
mov si,di ;SI->rest of line
jc @del_macsym_10 ;Not found so go onto next
; symbol
; The macro or symbol has been found. The current stack pointer is the
; first line of the expansion. Move it down to the separator just before
; the macro or symbol name itself. Then keep deleting strings from the
; stack until we hit another separator.
push cx ;Save remaining count
xor cx,cx
call near ptr strstk_bck_match ;Point to name
xor cx,cx
call near ptr strstk_bck_match ;Point to separator
@del_macsym_20:
call near ptr strstk_kill ;Delete the string
; Check if separator
call near ptr check_separator ;Is new 'current' a
; separator ?
jne @del_macsym_20 ;No, so keep deleting
; Reached a separator.
pop cx ;Restore count
jmp short @del_macsym_10 ;Keep looping for rest of line
@del_macsym_99:
mov sp,bp
pop bp
@restore
ret
del_macsym endp
;+
; FUNCTION : expand_macro, expand_symbol
;
; expand_macro and expand_symbol attempt expand the first word in
; linebuf as a macro name and symbol respectively. If no macro
; expansion is going on, the expand_macro routine will attempt to
; expand the first word in the line buffer as a macro (leading
; whitespace is ignored). If an expansion is found, it is stored
; in the line buffer with the parameters (if any) filled in. If
; there is already a macro expansion going on or no macro is
; found, the line buffer is unchanged.
;
; In contrast, the expand_symbol routine always tries to
; expand the first word as a symbol.
;
; If the first character of the line is a macro_ignore_char, the
; character is removed, the rest of the line moved up and no
; expansions are done.
;
; Parameters:
;
; Returns:
; CF = 0 if line buffer changed
; 1 otherwise (no macro/symbol or ongoing macro expansion)
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
expand_macro proc near
mov bx,offset DGROUP:mac_stk ;BX->macro descriptor
jmp short @expand
expand_symbol LABEL near
mov bx,offset DGROUP:sym_stk ;BX->symbol descriptor
@expand:
push si
push di
push bp
mov bp,sp
sub sp,2
num_remain equ <word ptr [bp-2]>
cmp bx,offset DGROUP:mac_stk
jne @expand_10 ;If symbol, don't worry about
; whether any macro expansions
; are ongoing
cmp macro_level,0 ;Already expanding a macro?
je @expand_10 ;No macro expansion currently ongoing
stc ;set CF to indicate no expansion
jmp @expand_98 ;Yes, exit with carry flag set
@expand_10:
; Look back through the macro/symbol stack buffer for a macro definition.
; Find first word of line.
push bx ;Save buffer descriptor addr
mov cx,lastchar ;End of line
mov si,offset DGROUP:linebuf ;SI->line buffer
sub cx,si ;CX<-length of line
call near ptr skip_whitespace ;SI->first non blank
mov di,si ;DI->start of word
call near ptr skip_nondelim ;SI->char after word
mov num_remain,cx ;Remember num chars remaining
; in line
mov ax,si ;
mov si,di ;SI->word
sub ax,si ;AX<-num chars in word
pop bx ;BX->buffer descriptor
call near ptr ismacsym ;Is it in macro/symbol stack ?
jc @expand_98 ;Not found so return with CF set
; The macro or symbol has been found.
; CHeck if it is the macro/symbol string stack
cmp bx,offset DGROUP:mac_stk ;Expanding a macro ?
jne @expand_50 ;No, expanding symbol
mov macro_level,1 ;Indicate expansion going on
; Copy linebuf into cur_macro (to remember arguments)
mov di,offset DGROUP:cur_macro
mov si,offset DGROUP:linebuf
mov cx,lastchar
sub cx,si ;CX<-length of linebuf
mov cur_macro_len,cx ;Store length of invocation line
rep movsb ;Remember macro invocation line
; The current stack string is the first expansion line of the macro
mov sp,bp ;clean up stack
pop bp ;restore registers
pop di
pop si
jmp short get_macro_line ;Yes, copy it into the linebuf buffer
; get_macro_line will return to caller with the appropriate value
; of the carry flag.
@expand_50:
; We are expanding a symbol.
; num_remain = num chars in linebuf after symbol
; First we move these characters to the end of the buffer
mov cx,num_remain ;CX<-number of chars to move back
call near ptr makeroom ;Make room in buffer
mov si,di ;SI->chars copied to the back
; Now copy the string
mov ax,offset DGROUP:linebuf ;AX->line buffer
mov cx,LINEBUF_SIZE ;CX<-size of line buffer
call near ptr strstk_copy ;Copy line. Never mind if end
; string overwritten.
; AX<-length of expanded string
mov di,offset DGROUP:linebuf
add di,ax ;DI->location after expansion
mov cx,num_remain ;CX<-num chars to copy after expansion
add ax,cx ;ax<-total length of line
cmp ax,LINEBUF_SIZE ;End chars overwritten ?
jbe @expand_60
mov ax,E_TRUNCATE ;Truncation error
jmp near ptr abort_processing
@expand_60:
; Copy trailing chars to end of expansion
rep movsb
mov lastchar,di
mov dot,di
clc ;Indicate expansion took place
@expand_98:
; MUST NOT CHANGE CARRY FLAG AFTER THIS POINT.
mov sp,bp ;clean up stack
pop bp ;restore registers
pop di
pop si
ret
expand_macro endp
;+
; FUNCTION : get_macro_line
;
; Copies a line from the macro stack to linebuf. All parameters
; codes (%1, %2 etc.) are replaced with their corresponding
; parameters. If this line is the last in the macro definition,
; the macro_level flag is reset. This allows the last line in a
; macro definition to be treated as a macro itself. In case of
; any errors( eg. expansion too long), the macro expansion is
; aborted and the input is directed to the keyboard.
;
; Parameters:
; None.
;
; Returns:
; CF = 0 if a line copied to linebuf
; 1 if no more lines in expansion or no ongoing expansion
; Register(s) destroyed:
; AX,BX,CX,DX
;-
get_macro_line proc near
cmp macro_level,0 ;Expanding a macro ?
jne @get_macro_line_1 ;Yes
stc ;CF = 1 for no expansion
ret
@get_macro_line_1:
push si
push di
push bp
mov bp,sp
sub sp,LINEBUF_SIZE
tempbuf equ <byte ptr [bp-LINEBUF_SIZE]>
; Copy the current string from the macro buffer into temp buffer.
mov bx,offset DGROUP:mac_stk ;BX->address of buffer descriptors
lea ax,tempbuf ;AX->destination address
mov cx,LINEBUF_SIZE ;CX<-size of buffer
call near ptr strstk_copy ;Copy macro into temp buffer
;AX<-length of expansion
; No error possible
; Replace any placeholders in the macro line by the corresponding paramters.
; copy all chars until first placeholder. Copy argument.
; Repeat for whole expansion. If at any time there is no place in
; buffer, then abort.
xchg cx,ax ;CX<-length of expansion
lea si,tempbuf ;SI->tempbuf (macro expansion)
mov di,offset DGROUP:linebuf ;DI->linebuf
@get_macro_line_38:
jcxz @get_macro_line_60 ;Jump if no more expansion
@get_macro_line_40:
; At the start of the loop, the following hold :
; DI->next empty location in the linebuf
; SI->next char of macro expansion to be examined (in tempbuf)
; CX = remaining number of chars in expansion (> 0)
mov al,PLACEHOLDER ;Going to search for placeholder
mov dx,cx ;DX<-length of remaining expansion
push di ;Save DI
mov di,si ;DI->start point for
; placeholder scan
repne scasb ;Look for placeholder
;assumes! (ES == SS)
; CX is number of chars after placeholder
sub dx,cx ;DX<-num chars to be copied
xchg dx,cx ;CX<-num chars to be copied
;DX<-num chars after placeholder
pop di ;DI->destination in linebuf
rep movsb ;Move chars from tempbuf to linebuf
;assumes! (ES == SS)
; Note we don't care if the characters at the end of the linebuf
; are overwritten.
mov cx,dx ;CX<-remaining number of chars
jcxz @get_macro_line_60 ;All chars copied (placeholder
; not found or last char in line)
; We have found a placeholder character in tempbuf. SI points to the
; character AFTER the placeholder. Based on the JCXZ above, there
; is at least one character after the placeholder. If it is a
; char between '0' and '9' then it is a genuine placeholder. If
; it is another placeholder character, then a single placeholder
; is stored. Else both the placeholder as well as the character
; will be stored into linebuf.
mov al,[si] ;AL<-char after placeholder
cmp al,PLACEHOLDER ;Is it a placeholder ?
jne @get_macro_line_45 ;No
; Skip over second placeholder
inc si
loop @get_macro_line_40
jmp short @get_macro_line_60 ;No more chars in expansion
@get_macro_line_45:
cmp al,'9'
ja @get_macro_line_40 ;Not a digit
sub al,'0'-1 ;Compaer with '0'. At the same
; time translate '0'->1,'1'->2
; ..and so on to '9'->10 since
; getargs counts from 1, not 0.
jbe @get_macro_line_40 ;Not a digit
dec di ;Cancel stored PLACEHOLDER char
push cx ;Save CX
mov bx,di ;BX->destination for argument
mov dx,LINEBUF_END
sub dx,bx ;DX<-remaining space in buffer
mov di,si ;Save SI in DI
mov si,offset DGROUP:cur_macro ;SI->current macro being expanded
xor ah,ah ;AX<-arg number (AL already
; holds actual arg number)
mov cx,cur_macro_len ;Length of original macro string
call near ptr getargs ;Get the argument into linebuf
;AX<-num chars copied, BX unchanged
;CF indicates error condition
pop cx ;Restore CX (num remaining
; chars in macro expansion)
mov si,di ;Restore SI
; (SI->char AFTER placeholder char)
mov di,bx ;Start of copied characters
jc @get_macro_line_101 ;Jump if getargs returned error
@get_macro_line_50:
add di,ax ;DI->next destination char in linebuf
inc si ;SI->next char of macro expansion
loop @get_macro_line_40 ;Decrement remaing characters
@get_macro_line_60:
mov lastchar,di ;Update end of line
mov dot,di ;Update cursor
; Set the lastchar and display end pointers
IF 0
Currently no need to set display pointers since macro lines are
not displayed
mov ax,di ;AX->Potential disp_end
mov dx,offset DGROUP:linebuf ;Potential disp_begin
call near ptr set_disp_marks ;Set the marks
ENDIF
; Finally check to see if this line is the last in the macro expansion.
mov bx,offset DGROUP:mac_stk ;BX->macro stack descriptor
xor cx,cx ;Move to next string in stack
call near ptr strstk_fwd_match
call near ptr check_separator ;Is this the last line of
; expansion ?
jne @get_macro_line_90 ;No
; This was the last line in the macro expansion. Reset macro flag
mov macro_level,0 ;Reset flag
@get_macro_line_90:
; @unlink
mov sp,bp ;clean up stack
pop bp ;restore registers
pop di
pop si
clc ;Return macro expanded
ret
@get_macro_line_101:
; truncation error
mov ax,E_TRUNCATE ;Indicate truncation of line
jmp near ptr abort_processing
get_macro_line endp
;+
; FUNCTION : check_separator
;
; Checks to see if the current macro/symbol buffer line is a
; separator.
;
; Parameters:
; BX = address of macro/symbol stack descriptor
;
; Returns:
; ZF = 1 if current buffer line is the separator string
; 0 otherwise.
;
; Register(s) destroyed:
; AX,CX
;-
check_separator proc near
mov ax,offset DGROUP:separator
mov cx,sep_len
call near ptr strstk_compare ;Is it the separator ?
; strstk_comapre sets ZF.
ret
check_separator endp
;+
; FUNCTION : execute_defs
;
; Called to define a symbol.
;
; Parameters:
; SI -> first char in linebuf following this command
; CX == remaining num chars in the line
;
; Returns:
; Nothing.
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
execute_defs proc near
mov bx,offset DGROUP:sym_stk ;BX->stack descriptor
; Push macro name
call near ptr push_word ;Push first word onto
; the stack. Params :
; SI->string
; CX=num chars
; Returns:
; AX<-status code
; SI->char after word
; CX<-num remaining chars
cmp ax,0 ;Check status
jg @execute_defs_99 ;No word on line,
; ignore command
jl @execute_defs_109 ;No room in stack
; Now push the rest of the string as is except that leading whitespace
; is compressed.
call near ptr skip_whitespace ;Skip leading whitespace
;Params SI, CX
;Returns SI->first
; non-white space char
; CX<-remaining chars
; (maybe 0)
call near ptr push_string ;Push string onto stack
; (maybe null string)
jc @execute_defs_109 ;No room in stack
call near ptr cmdsym_separate ;Push separator onto stack
jc @execute_defs_109 ;No room in stack
@execute_defs_99:
ret
@execute_defs_109:
; Error. No room in macro stack.
call near ptr cmdsym_cleanup ;Clear out partial definition
call near ptr disp_noroom ;Display error
ret
execute_defs endp
;+
; FUNCTION : execute_defm
;
; Called to define a multiple line macro. This function will
; keep reading from the current input source and storing it in
; the macro buffer until an `endm' is seen. The ENDM directive
; can be followed by any characters (eg. macro name)
;
; Parameters:
; SI -> first char in linebuf following this command
; CX == remaining num chars in the line
;
; Returns:
; Nothing.
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
execute_defm proc near
mov ax,E_NESTED_MACRO ;Assume error
cmp macro_level,0 ;Expanding a macro ?
je @execute_defm_1 ;No, jump
jmp near ptr abort_processing ;Yes, nested macro error
@execute_defm_1:
@save si,di
push bp
mov bp,sp
sub sp,2
err_flag equ <word ptr [bp-2]>
mov bx,offset DGROUP:mac_stk ;BX->stack descriptor
mov err_flag,0 ;Initially no errors
; If expanding a macro, ignore all lines until an endm
cmp macro_level,0
jnz @execute_defm_5 ;Go set error flag
; Push macro name
call near ptr push_word ;Push first word onto
; the stack. Params :
; SI->string
; CX=num chars
; Returns:
; AX<-status code
; SI->char after word
; CX<-num remaining chars
cmp ax,0 ;Check status
jb @execute_defm_5 ;No room in stack
je @execute_defm_40 ;No errors
; No name for macro. Push a 0 length macro name. Macro can be called by
; a blank line
xor cx,cx ;CX<-0 (length of string)
call near ptr push_string ;Push onto stack
jnc @execute_defm_40 ;No errors
@execute_defm_5:
mov err_flag,1 ;Indicate error
call near ptr cmdmacro_cleanup ;Cleanup macro fragments
@execute_defm_40:
; Keep reading lines from the input and store in macro buffer unless an error
; has been previously seen.
; Prompt is displayed only if reading from the keyboard.
cmp source,offset DGROUP:get_kbd_line
jne @execute_defm_42
@DispStr macro_prompt
call near ptr reset_line
@execute_defm_42:
call near ptr get_next_line
mov si,offset DGROUP:linebuf ;SI->String to push
mov cx,lastchar
sub cx,si ;CX<-length of line
push si ;Remember SI and CX
push cx
call near ptr skip_whitespace ;SI->first non-white
;CX<-num remaining chars
jcxz @execute_defm_50 ;Blank line so go store it
mov di,si ;DI->start of word
call near ptr skip_nonwhite
mov cx,si
sub cx,di ;CX<-length of word
xor ah,ah
mov si,offset DGROUP:endm_cmd
lodsb ;AX<-length of endm command
cmp cx,ax
jne @execute_defm_50 ;Not ENDM
call near ptr stre_cmp ;Compare strings
jnz @execute_defm_50
; ENDM seen.
cmp err_flag,1 ;Had we seen an error ?
jne @execute_defm_45 ;No
call near ptr disp_noroom ;Display error
jmp short @execute_defm_99
@execute_defm_45:
; End of macro seen. Store macro separator
call near ptr cmdmacro_separate
jmp short @execute_defm_99
@execute_defm_50:
; Store line in macro buffer
pop cx ;CX<-length of line
pop si ;SI->linebuf
mov bx,offset DGROUP:mac_stk ;BX->stack descriptor
call near ptr push_string
jnc @execute_defm_40 ;No error
jmp short @execute_defm_5 ;Indicate error
@execute_defm_99:
mov sp,bp
pop bp
@restore
ret
execute_defm endp
;+
; FUNCTION : cmdmacro_separate,cmdsym_separate,separate
;
; Pushes a separator string onto the stack.
;
; Parameters:
; None for cmdmacro_separate and cmdsym_separate
; BX->buffer descriptor for routine separate
; Returns:
; CF = 1 if no room in stack
; 0 otherwise
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
cmdmacro_separate proc near
mov bx,offset DGROUP:mac_stk ;BX->stack descriptor
jmp short separate
cmdsym_separate LABEL near
mov bx,offset DGROUP:sym_stk ;BX->stack descriptor
separate LABEL near
mov ax,sep_len ;AX<-length of separaot string
mov dx,offset DGROUP:separator ;DX->separator string
mov cx,1 ;Force push onto stack
call near ptr strstk_push ;Returns status in CF
ret
cmdmacro_separate endp
;+
; FUNCTION : cmdmacro_cleanup,cmdsym_cleanup
;
; This function is called to clean up the top of the macro or symbol
; stacks when a complete definition cannot be pushed onto the
; stack due to lack of space. The routine keeps deleting strings
; from the top of the macro stack until it finds a separator string.
;
; Parameters:
; None.
;
; Returns:
; Nothing.
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
cmdmacro_cleanup proc near
mov bx,offset DGROUP:mac_stk ;BX->stack descriptor
jmp short @cleanup_5
cmdsym_cleanup LABEL near
mov bx,offset DGROUP:sym_stk ;BX->stack descriptor
@cleanup_5:
call near ptr strstk_settop ;Reset cur pointer
; BX unchanged
; While the top of stack is not a separator, keep killing strings.
@cleanup_10:
call near ptr check_separator
; mov ax,offset DGROUP:separator ;AX->separator string
; mov cx,sep_len ;CX<-length of separator
; call near ptr strstk_compare ;Is this a separator ?
je @cleanup_99 ;Yes, then all done
call near ptr strstk_kill ;Kill string
jmp short @cleanup_10 ;Keep going
@cleanup_99:
ret
cmdmacro_cleanup endp
;+
; FUNCTION : disp_noroom
;
; Displays a message saying there is no room in the macro buffer.
;
; Parameters:
; None.
;
; Returns:
; Nothing.
; Register(s) destroyed:
; AX,DX
;-
disp_noroom proc near
@DispStr macro_noroom_msg
ret
disp_noroom endp
;+
; FUNCTION : search_variable
;
; Called to search the passed string for a variable. A variable is a
; sequence of characters starting with the VAR_MARKER character
; followed by another VAR_MARKER character. Two marker characters in
; succession are left untouched. (This is so that this routine can be
; called again to check for more variables.)
;
; Parameters:
; SI - points to the string.
; AX - length of the string.
;
; Returns:
; SI - Points to first marker character of variable if AX is not 0
; else points to end of string.
; AX - length of variable (including marker chars) if
; variable is present, else 0
; Register(s) destroyed:
; CX
;-
search_variable proc near
@save di
mov cx,ax
@search_variable_10:
mov di,si ;DI->string to search
; CX is number of chars
; Note we are OK if length of line is already 0.
mov al,VAR_MARKER
repne scasb ;Search for marker
; If marker not found or found in last position, CX will be 0.
jcxz @search_variable_99
; DI->char after first marker, CX is remaining number of characters
mov si,di
@search_variable_20:
lodsb ;AL<-next char
dec cx
jcxz @search_variable_99 ;If there was only one char
; after the marker, variable
; not possible
cmp al,VAR_MARKER ;Is it a marker ?
je @search_variable_10 ;Yes, found a marker, just
; ignore the pair and keep
; looking
; We have found the start of a variable. Look for its end.
inc di ;Move past first char of var
mov al,VAR_MARKER
repne scasb ;Look for it
jne @search_variable_99 ;Not found (CX is 0)
; Found a variable
dec si
dec si ;SI->start marker of var
mov cx,di
sub cx,si ;CX<-length of var
@search_variable_99:
xchg ax,cx ;AX<-length of var
@restore
ret
search_variable endp
;+
; FUNCTION : get_symbol
;
; Hunts through the symbol stack looking for a match for the passed
; symbol. If found, it is returned in passed buffer.
;
; Parameters:
; SI - points to the symbol
; AX - length of symbol
; DI - points to the expansion buffer
; DX - size of buffer
;
; Returns:
; CF - 1 symbol not found (AX set to 0) or [DI] buffer to small
; to hold expansion (AX contains actual length of expansion).
; 0 symbol found. Its expansion is stored in [DI] with
; length in AX.
; Register(s) destroyed:
; AX,BX,CX,DX
;-
get_symbol proc near
@save si
mov bx,offset DGROUP:sym_stk ;BX->symbol stack
push dx ;Save DX
call near ptr ismacsym ;Is it a symbol ?
pop cx ;CX<-size of buffer
jnc @get_symbol_20 ;Yes, go on
xor ax,ax ;No, not a symbol
stc ;Set CF to indicate error
jmp short @get_symbol_99
@get_symbol_20:
; Symbol has been found. The current symbol stack pointer has been set to
; its expansion string.
mov ax,di ;AX->buffer
call near ptr strstk_copy ;Params
; BX->stack descriptor
; AX->buffer
; CX->size of buffer
; Returns CF set if
; truncation error.
; AX always set to length of
; actual expansion.
; CF set/reset by strstk_copy
@get_symbol_99:
@restore
ret
get_symbol endp
;+
; FUNCTION : get_dosenv
;
; Hunts through the DOS environment looking for a match for the passed
; string. If found, it is returned in passed buffer.
;
; Assumes passed string does NOT contain a NULL byte.
;
; Parameters:
; SI - points to the string
; AX - length of string
; DI - points to the expansion buffer
; DX - size of buffer
;
; Returns:
; CF - 1 string not found (AX set to 0) or [DI] buffer too small
; to hold expansion (AX contains actual length of expansion).
; 0 symbol found. Its expansion is stored in [DI] with
; length in AX.
; Register(s) destroyed:
; AX,BX,CX,DX
;-
get_dosenv proc near
@save si,di,es
push bp
mov bp,sp
sub sp,6
var_len equ <word ptr [bp-2]>
exp_buf equ <word ptr [bp-4]>
exp_buf_len equ <word ptr [bp-6]>
mov var_len,ax ;Save length of string
mov exp_buf,di ;Save expansion buf address
mov exp_buf_len,dx ;Save expansion buf length
call near ptr locate_dosenv ;AX->DOS environment segment
or ax,ax ;Is it 0?
je @get_dosenv_90 ;Yes, we do not know DOS env
; We have the DOS segment
mov es,ax ;ES->environment segment
xor di,di ;ES:DI->base of environment
mov cx,8000h ;Max possible environ size
; is 32K
@get_dosenv_10:
; Top of env search loop. DI contains offset into environment. SI points to
; string. DI actually points to start of an env var name if any. CX is
; remaining bytes in environment (> 0)
cmp byte ptr es:[di],0 ;End of environment?
je @get_dosenv_89 ;Yes
; We are just going to call stre_cmp to compare strings. Does not matter if
; the environment or environment var ends before then since the comparison
; will fail since we are assuming a null byte does not occur in the string
; that was passed to us.
push cx ;Remember how much
; environment left
mov cx,var_len ;CX<-length to compare
call near ptr stre_cmp ;Params DS:SI, ES:DI, CX
pop cx ;CX<-remaining environment size
je @get_dosenv_50 ;Match so far
; No match. Hunt for the next null byte or end of environment.
@get_dosenv_40:
xor al,al
repne scasb
jcxz @get_dosenv_89 ;If no match for null or
; match in last byte, exit
jmp short @get_dosenv_10 ;Keep looking
@get_dosenv_50:
; We have a match so far. The next char in the environ must be a '='.
mov ax,var_len
add di,ax
sub cx,ax
cmp byte ptr es:[di],'='
jne @get_dosenv_40 ;No match
; We have a match.
inc di ;DI->start of env expansion
push di ;Save DI
dec cx ;CX<-remaining length of env
xor ax,ax
repne scasb ;Hunt for expansion end
mov ax,di
pop di ;DI->start of expansion
sub ax,di
dec ax ;AX<-length of expansion
mov cx,exp_buf_len ;CX<-size of expansion buffer
cmp cx,ax ;Is it too small?
pushf ;Remember flags
jb @get_dosenv_60 ;Copy only that many bytes
mov cx,ax ;Copy all bytes of expansion
@get_dosenv_60:
; AX contains number of bytes so do not change it after this.
push ds ;Save DS
push es
pop ds
pop es
mov si,di ;DS:SI->expansion of env var
mov di,exp_buf ;ES:DI->expansion buffer
rep movsb
push es
pop ds ;Restore DS. ES is restored
; at end of routine.
popf ;Restore status in CF.
; AX already contains
; expansion actual byte count
jmp short @get_dosenv_99
@get_dosenv_89:
; Not found in environment
xor ax,ax
@get_dosenv_90:
; Error. AX must have been already set appropriately.
stc
@get_dosenv_99:
mov sp,bp
pop bp
@restore
ret
get_dosenv endp
;+
; FUNCTION: replace_var_markers
;
; Replaces all double VAR_MARKER characters with single VAR_MARKERs.
; Any solo VAR_MARKERs are deleted (as in DOS batch files).
;
; Parameters:
; DI - points to string
; CX - length of string
;
; Returns:
; AX - new length of string
;
; Registers destroyed:
; AX,CX,DX
;-
replace_var_markers proc near
@save si,di
mov dx,cx
mov al,VAR_MARKER
@replace_var_markers_9:
jcxz @replace_var_markers_99
@replace_var_markers_10:
; CX must not be 0 at this point! Else calculation of DX goes wrong.
repne scasb ;Look for marker
jne @replace_var_markers_99 ;No more markers in string
; Found a marker. Move remaining characters up by 1.
; DI->first char to be moved up, CX is number of chars to move up
dec dx ;Deleted one char
jcxz @replace_var_markers_99 ;No more bytes
push cx ;Sace byte count
push di ;Save di
mov si,di
dec di
rep movsb ;Move chars up
pop di ;DI->next pos to start hunt
pop cx ;remaining bytes
dec cx ;jump over character that
; now occupies the original
; marker position.
jmp short @replace_var_markers_9
@replace_var_markers_99:
mov ax,dx ;AX<-new string length
@restore
ret
replace_var_markers endp
;+
; FUNCTION : expand_var
;
; Expands the variables (if any) present in the current line. A
; variable consists of a series of non-delimiter characters
; between two 'VAR_MARKER' characters. The DOS environment block is
; first checked for the presence of the first such variable in the
; line and if it is present, it is replaced with its value. Otherwise
; the CMDEDIT symbols are checked for match. If there is a match, it
; is replaced with the value of the symbol else, it is replaced by a
; null string. This procedure is repeated until there are no symbols
; in the line. Note that the replacements may themselves contain
; variables which will be replaced in turn.
;
; Parameters:
; None.
;
; Returns:
; CF = 0 if no errors.
; CF = 1 if errors (line too long). AX contains error code.
; Register(s) destroyed:
; AX,BX,CX,DX
;-
expand_var proc near
@save si,di
push bp
mov bp,sp
sub sp,LINEBUF_SIZE
var_exp equ <byte ptr [bp-LINEBUF_SIZE]>
@expand_var_10:
mov si,offset DGROUP:linebuf ;SI->current line
mov ax,lastchar
sub ax,si ;AX<-length of line
call near ptr search_variable ;Look for a var
; AX<-length of var
; SI-> var
or ax,ax ;AX is length of var
je @expand_var_60 ;No more vars,
; First check the DOS environment for the presence of this variable.
mov var_len,ax ;Store var len in var_len
inc si ;Move SI past the VAR_MARKER
dec ax
dec ax ;Do not count the two
; VAR_MARKER characters
lea di,var_exp ;DI->buffer
mov dx,LINEBUF_SIZE ;DX<-size of expansion buffer
call near ptr get_dosenv ;Params SI->var, AX length
; DI->var_exp
; Returns expansion in [DI]
; AX length of expansion
or ax,ax ;Any expansion ?
jne @expand_var_50 ;Yes - go store in line
; DOS environment did not have the var, now try the CMDEDIT symbol stack.
mov ax,var_len
dec ax
dec ax ;Do not count the two
mov dx,LINEBUF_SIZE ;DX<-size of expansion buffer
call near ptr get_symbol ;Params SI,AX,DI,DX
; Returns expansion in [DI],
; length in AX
; Even if expansion was null, fall thru and replace var with the
; expansion (possibly null)
@expand_var_50:
; Replace the var pointed to by SI of length var_len by the expansion given
; in var_exp (with length in AX).
dec si ;SI->points to marker
mov di,ax ;Store AX in DI
mov ax,var_len
call near ptr remove_chars ;SI,AX parameters
mov ax,di ;Restore length of expansion
mov di,si ;DI->destination
lea si,var_exp
; push dot ;Save position
call near ptr insert_chars ;SI,DI,AX parameters
; pop dot
jnc @expand_var_10 ;No error
mov ax,E_TRUNCATE ;Truncation error
jmp short @expand_var_99 ;Exit, CF already has status
@expand_var_60:
; All variables have been replaced. Now change all double marker characters
; to single and delete all solo markers (similar to DOS)
mov di,offset DGROUP:linebuf ;DI->string
mov cx,lastchar
sub cx,di ;CX<-length of string
call near ptr replace_var_markers
add ax,di
mov lastchar,ax
cmp dot,ax
jb @expand_var_80
mov dot,ax
@expand_var_80:
mov dx,offset DGROUP:linebuf
call near ptr set_disp_marks ;Assume all chars changed.
clc ;CF<-0 (no errors)
@expand_var_99:
; CF has status
mov sp,bp
pop bp
@restore
ret
expand_var endp
;+
; FUNCTION: disp_until_sep
;
; This function outputs the contents of the specified stack until the
; separator is seen. Each element of the stack is followed by a
; CR-LF. The current pointer is left pointing to the separator.
;
; Parameters:
; BX - pointer to stack descriptor
;-
disp_until_sep proc near
@save si,di
push bp
mov bp,sp
sub sp,LINEBUF_SIZE+2
display_buf equ <byte ptr [bp-LINEBUF_SIZE]>
sym_kluge equ <byte ptr [bp-LINEBUF_SIZE-2]> ;Kluge to not output extra
; space after defs line
mov sym_kluge,1
@disp_until_sep_10:
; Start of loop. Check if the current entry is a separator. If so exit.
; Else display the line.
call near ptr check_separator
je @disp_until_sep_99 ;Yes, all done
; Copy the string into the display buffer and display it.
lea ax,display_buf
mov cx,LINEBUF_SIZE
call near ptr strstk_copy
xchg cx,ax ;CX<-length of string
push bx ;Save BX
lea dx,display_buf ;DX->string, CX is count
call near ptr output_counted_string
pop bx
cmp bx,offset DGROUP:mac_stk
jne @disp_until_sep_30
call near ptr output_newline
jmp short @disp_until_sep_32
@disp_until_sep_30:
cmp sym_kluge,0
je @disp_until_sep_32
@DispCh ' '
@disp_until_sep_32:
xor cx,cx
call near ptr strstk_fwd_match
mov sym_kluge,0
jmp short @disp_until_sep_10
@disp_until_sep_99:
mov sp,bp
pop bp
@restore
ret
disp_until_sep endp
;+
; FUNCTION: output_symbols,output_macros
;
; Outputs to standard output the current buffer contents of
; the symbol/macro buffer.
;
; Parameters:
; None.
;-
output_macsym proc near
output_symbols LABEL near
mov bx,offset DGROUP:sym_stk
mov ax,offset DGROUP:defs
jmp short @output_macsym_5
output_macros LABEL near
mov bx,offset DGROUP:mac_stk
mov ax,offset DGROUP:defm
@output_macsym_5:
@save si,di
mov di,ax ;DI->'defs' or 'defm'
call near ptr strstk_save_cur ;Save current pointer.
; Probably not necessary for
; symbols, but do anyway
call near ptr strstk_setbot ;Go to bottom of stack
call near ptr output_newline
@output_macsym_10:
; Start of loop. One entire macro or symbol has been processed so far.
; The current stack pointer is at the separator. First skip over the separator.
xor cx,cx
call near ptr strstk_fwd_match
; Try to move one more to make sure there is another string
xor cx,cx
call near ptr strstk_fwd_match
jc @output_macsym_99 ;End of buffer
; OK move back to get the name of the symbol / macro.
xor cx,cx
call near ptr strstk_bck_match
; Now output either 'defs' or 'defm'
mov dx,di
inc dx ;Point to string
xor cx,cx
mov cl,[di] ;Length of string
push bx ;Save BX
call near ptr output_counted_string ;Show command string
@DispCh ' ' ;followed by a space
pop bx
; Now display all strings until a separator is seen
call near ptr disp_until_sep
; Now if we are showing a macro, output the 'endm', else a newline
cmp bx,offset DGROUP:mac_stk ;Macro ?
jne @output_macsym_70 ;No, repeat loop
; Output the 'endm'
push bx
mov bx,offset DGROUP:endm_cmd
xor cx,cx
mov cl,[bx]
mov dx,bx
inc dx
call near ptr output_counted_string ;AX,BX,CX,DX destroyed
pop bx
call near ptr output_newline ;Separate each macro with a
; newline
@output_macsym_70:
call near ptr output_newline
jmp @output_macsym_10
@output_macsym_99:
call near ptr strstk_restore_cur ;Restore the current pointer
@restore
ret
output_macsym endp
;+
; FUNCTION: execute_cmdstat
;
; Displays the current macro and symbol buffer contents.
;
;
; Parameters:
; SI -> first char in linebuf following this command
; CX == remaining num chars in the line
;
; Returns:
; Nothing.
;
; Register(s) destroyed:
; AX,BX,CX,DX
;-
execute_cmdstat proc near
call near ptr output_symbols
call near ptr output_macros
call near ptr output_newline
ret
execute_cmdstat endp
CSEG ENDS
END