; CMDMACRO.ASM
; (c) 1989, 1990 Ashok P. Nadkarni
;
; Module implementing macro and symbol feature for CMDEDIT.
; Brace expansion added 10 & 11 March, 1997, by Jason Hood.
; Removed most commented code, jmh, 9 May.
; Have separate buffers for applications, jmh, 21 December, 2001.
;
; 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.
;
; Parameters can be followed by a '*' to represent all following parameters.
; '%*' is equivalent to '%1*' is (almost) equivalent to '%1 %2 %3 %4 %5 %6 %7
; %8 %9'. It's almost equivalent since '%*' just copies the rest of the line
; (similar to symbols) but '%1 .. %9' stops at the ninth parameter.
;
; 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
; carriage return on a blank line will result in that macro being run.
;
; Brace Expansion:
;
; Brace expansion is when a comma-separated list of items within braces ({}) is
; expanded with what comes before and after the braces.
; If the line is:
;
;	file{1,2}.tmp
;
; the expanded form is:
;
;	file1.tmp file2.tmp
;
; where "file" is the prepend and ".tmp" is the postpend. The -pends are
; terminated by spaces (or tabs), commas, semicolons and plus signs.
;
;	file1;file{2,3},file{4,5}
;
; will become:
;
;	file1;file2;file3,file4,file5
;
; If the prepend terminator is a space, the postpend terminator will be used.
;
; Braces can be nested and the items can include spaces. It is also possible
; to have an empty string
;
;	file{1, 2}   becomes	file1 file 2
;	file{,~}     becomes	file file~
;
; If the braces are ill-formed (either too few, or too many) or it's been
; incorrectly quoted (mismatched quotes), then no expansion takes place.

	INCLUDE common.inc
	INCLUDE general.inc
	INCLUDE ascii.inc
	INCLUDE dos.inc

	PUBLIC	execute_defm
	PUBLIC	execute_defs
	PUBLIC	execute_delm
	PUBLIC	execute_dels
	PUBLIC	execute_rstsym
	PUBLIC	execute_rstmac
	PUBLIC	get_macro_line
	PUBLIC	getparms		;added by jmh
	PUBLIC	multi_cmd		;ditto
	PUBLIC	get_multi_line		;ditto 980510
	PUBLIC	associate		;ditto 980513
	PUBLIC	expand_macro
	PUBLIC	expand_symbol
	PUBLIC	expand_var
	PUBLIC	expand_braces
	PUBLIC	get_symbol
	PUBLIC	mac_stk
	PUBLIC	sym_stk
	PUBLIC	mac_ptr
	PUBLIC	sym_ptr
	PUBLIC	ismacsym
	PUBLIC	get_dosenv		;made public by jmh
	PUBLIC	check_separator 	;ditto 980507
	PUBLIC	separate		;ditto 980509
	PUBLIC	crlf$			;added by jmh 011219

	PUBLIC	Macro_instance_offset		;Added by jmh
	PUBLIC	Macro_instance_size

PLACEHOLDER equ PERCENT				;Placeholder character

	INCLUDE buffers.inc

CSEG	SEGMENT	BYTE 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:BYTE	; changed by jmh - was word
	EXTRN	cmd_level:WORD		; added by jmh
	EXTRN	macro_ignore_char:BYTE
	EXTRN	lastchar:WORD
	EXTRN	linebuf:BYTE
	EXTRN	dot:WORD
	EXTRN	linelimit:WORD		;changed from LINEBUF_END jmh 980512
	EXTRN	cur_macro:BYTE
	EXTRN	cur_macro_len:WORD
	EXTRN	temp1:WORD		;these four added by jmh 980512
	EXTRN	temp2:WORD
	EXTRN	pend_list:BYTE
	EXTRN	pend_len:ABS

Macro_instance_offset = $
mac_stk $string_stack	2 DUP (<>)	;Descriptor for macro buffer
sym_stk $string_stack	2 DUP (<>)	;Descriptor for sym buffer
mac_ptr 	dw	?		;Current macro buffer (DOS/app)
sym_ptr 	dw	?		;Current symbol buffer (DOS/app)
del_one 	db	0		;Flag to delete only one macro/sym (jmh)
Macro_instance_size = $ - offset Macro_instance_offset
separator	db	13		;Separator string between macros
sep_len 	equ	$-separator	;Length of separator string
;jmh 980507: indicate which table is full
noroom_sym	db 'Symbol',DOLLAR
noroom_mac	db 'Macro',DOLLAR
noroom_msg	db ' table full. Definition ignored.'
crlf$		db CR,LF,DOLLAR 	;jmh 011219: used by output_newline
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	line_too_long:PROC	;added by jmh 990520
	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	get_line_len:PROC	;added by jmh 980517
	EXTRN	remove_chars:PROC
	EXTRN	insert_chars:PROC
	EXTRN	output_newline:PROC
	EXTRN	locate_dosenv:PROC
	EXTRN	strlen:PROC		;added by jmh 980623
	EXTRN	get_string:PROC 	;added by jmh 990522
	EXTRN	test_quote:PROC 	;moved to utl.asm by jmh 990522

	ASSUME	CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP


;+
; 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
;	shr	macro_level,1
	mov	bx,mac_ptr		;bx := address of buffer descriptors
	jmp	short @rst_macsym
execute_rstsym LABEL near
	mov	bx,sym_ptr		;bx := address of buffer descriptors
@rst_macsym:
	call	near ptr strstk_reset	;Re-initialize buffer and descriptor

;Store a separator into the macro buffer
	jmp	near ptr separate
;	ret				;commented jmh 990517
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

	xchg	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
	call	near ptr check_separator
	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,sym_ptr
	jmp	short @del_macsym_5
execute_delm LABEL near
	cmp	macro_level,0
	je	@execute_delm_1
	mov	al,E_NESTED_DELM
	jmp	near ptr abort_processing
@execute_delm_1:
	mov	bx,mac_ptr
@del_macsym_5:
	@save	si,di

@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_30			;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
@del_macsym_30: 				;added by jmh
	shr	del_one,1
	jnc	short @del_macsym_10		;Keep looping for rest of line

@del_macsym_99:
	@restore
	ret
del_macsym endp



;+
; FUNCTION: multi_cmd by jmh (15 March, 1997)
;
;	determine if there's more than one command on the line. If there is
;	set cmd_level to the length of the entire line, copy the line to
;	cur_macro, set cur_macro_len to the length of the first command and
;	make the linebuf the first command.
;
; Parameters:
;
; Returns:
;	cmd_level = length of entire line if separator is present
;		    0 if no separator.
;
; Registers destroyed:
;	AX,BX,CX
;-
multi_cmd proc near
	call	near ptr get_line_len
	mov	di,si			;DI -> line buffer
	jcxz	@multi_cmd_20
	mov	bx,cx			;BX,CX = length of line
	xor	ah,ah			;Quote flag
@multi_cmd_10:
	call	test_quote
	loopnz	@multi_cmd_10
	jcxz	@multi_cmd_20
	cmp	al,CTL_S
	jne	@multi_cmd_10
	cmp	byte ptr [si-2],VAR_MARKER ;jmh 980516: allow "%" to quote
	je	@multi_cmd_10		   ; the separator character
	dec	si			;SI -> separator
	mov	cx,si
	sub	cx,di			;CX -> len of first cmd
	mov	cur_macro_len,cx
	mov	lastchar,si		;The separator is now eol
	mov	si,offset DGROUP:cur_macro ;Copy the whole line to cur_macro
	xchg	si,di
	mov	cx,bx
	rep	movsb
	mov	cx,bx
@multi_cmd_20:
	mov	cmd_level,cx
	ret
multi_cmd endp


;+
; FUNCTION : get_multi_line (jmh 980510)
;
;	Get the next command on the line, if any.
;
; Parameters:
;	None.
;
; Returns:
;	CF	= 0 if a line copied to linebuf
;		  1 if no more commands, or only one command
; Register(s) destroyed:
;	AX,CX
;-
get_multi_line	proc near
	mov	cx,cmd_level		;CX = length of entire command line
	stc				;Assume CF = 1 for no more commands
	jcxz	@get_multi_line_99	; when CX is zero

	@save	si,di
	mov	si,offset DGROUP:cur_macro
	mov	ax,cur_macro_len	;AX = length of first command
	inc	ax			;include the separator
	add	si,ax			;SI -> character after separator
	sub	cx,ax			;CX = length of second... commands
	mov	di,offset DGROUP:linebuf
	rep	movsb
	mov	lastchar,di
	;clc				;The subtract will clear carry
	@restore
@get_multi_line_99:
	ret
get_multi_line endp



;+
; FUNCTION : associate by jmh 980513 & 17
;
;	If the first word ends in '/' or '\', assume the line contains a
;	directory and insert the expansion of the symbol "\" at the
;	beginning of the line. Convert an ending '/' to '\', and remove it
;	if it's trailing (other slashes are left alone, however).
;
;	If the first word has an extension, search the extension list for it
;	and insert the expansion at the beginning of the line.
;
; Parameters:
;	None.
;
; Returns:
;	CF =	0 if line buffer changed
;		1 otherwise (no association found)
;
; Registers destroyed:
;	AX,BX,CX,DX
;
; jmh 980622: first word must be the start of the line;
;	      directory association removes the trailing backslash.
;
; jmh 990522: modified to handle quoted names;
;	      no longer requires first word to be at the start (I don't know
;	       why I did that, anyway).
; jmh 990523: actually delete trailing backslash (used to replace with space).
; jmh 990526: added carry flag return status to allow dosify/ignore to be used.
; jmh 010403: remove an appended '=', treating it as an alternative assoc.
;-
associate proc near
	@save	si,di
	call	near ptr get_line_len	;SI->line, CX<-length
	mov	ah,2			;Strip quotes
	call	near ptr get_string	;DI->beginning, SI->end+1
	jc	@associate_90_a		;Line is blank
	dec	si			;SI->last character in word
	std				;Searching backwards
	lodsb
@associate_10:
	cmp	al,'\'
	je	@associate_20
	cmp	al,'/'
	jne	@associate_25
@associate_20:
	cld
	inc	si
	mov	byte ptr [si],'\'       ;Symbol to search
	mov	ax,1			;Its length
	mov	bx,sym_ptr		;Its location
	call	near ptr ismacsym	;Find it
	jc	@associate_90		;...or not
	cmp	si,di			;Root directory this drive?
	je	@insert_symbol		;Yes, leave alone
	cmp	byte ptr [si-1],':'     ;Root directory of another drive?
	je	@insert_symbol		;Yes, leave alone
	mov	ax,1			;Remove the trailing backslash
	call	near ptr remove_chars
	jmp	short @insert_symbol

@associate_25:
	xor	cx,cx			;Length of extension
@associate_30:				;Search for an extension
	cmp	si,di
	jb	@associate_80		;No extension
	cmp	al,'.'
	je	@associate_40		;Extension found
	cmp	al,'/'
	je	@associate_80
	cmp	al,'\'
	je	@associate_80
	cmp	al,':'
	je	@associate_80
	lodsb
	inc	cx
	jmp	short @associate_30

@associate_40:
	cmp	byte ptr [si],'.'       ;If the previous character is a dot
	je	@associate_80		; assume it's not a filename (this is
					; intended to allow ".." for "cd..").
	cld
	inc	si
	inc	si			;SI->points to extension
	push	cx			;Preserve length
	call	strstk_ext_find 	;Find it
	pop	cx
	jc	@associate_90		;No match
	add	si,cx
	dec	si
	cmp	byte ptr [si],'='       ;Alternative association? (jmh 010403)
	jne	@insert_symbol
	mov	ax,1			;Remove the equal sign
	call	remove_chars
@insert_symbol: 			;Entry point for expand_symbol
	xor	cx,cx			;Find the length of the expansion
	call	strstk_copy
	push	ax
	inc	ax			;One more for the space
	mov	di,offset DGROUP:linebuf
	call	insert_chars		;Insert that many characters
	pop	cx
	jc	@associate_60
	mov	ax,di
	call	strstk_copy		;Will clear carry
	xchg	ax,bx
	mov	byte ptr [di+bx],SPACE
@associate_90_a:
	jmp	short @associate_90

@associate_80:
	cld
	stc
@associate_90:
	@restore
	ret

@associate_60:
	jmp	near ptr line_too_long
associate endp



;+
; FUNCTION : expand_macro, expand_symbol
;
;	expand_macro and expand_symbol attempt to 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.
;
; 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_symbol LABEL near
	mov	bx,sym_ptr		;BX->symbol descriptor
	jmp	short @expand_10

expand_macro proc near
	cmp	macro_level,0		;Already expanding a macro?
	je	@expand 		;No macro expansion currently ongoing
	stc				;set CF to indicate no expansion
	ret				;Yes, exit with carry flag set
@expand:
	mov	bx,mac_ptr		;BX->macro descriptor

@expand_10:
	@save	si,di

; Look back through the macro/symbol stack buffer for a macro definition.
;	Find first word of line.
	push	bx			;Save buffer descriptor addr
	call	near ptr get_line_len
	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	temp2,si		;Remember position
; jmh 980512: temp1 is used by strstack_bck_match (perhaps I should have kept
;	      the stack, but I'm not going to change it again)

	xchg	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,mac_ptr		;Expanding a macro ?
	je	@expand_50		;Yep

; We are expanding a symbol. Rewritten by jmh 980517.
	mov	si,temp2
	cmp	byte ptr [si],' '       ;jmh 981114: delete space as well
	jne	@expand_30
	inc	si
@expand_30:
	xchg	ax,si			;AX->end of symbol
	mov	si,offset DGROUP:linebuf
	sub	ax,si			;AX->number of characters to remove
	call	remove_chars		;Delete the symbol name
; Jump to the insert routine in associate, which will return correctly.
; This is bad programming, but it saves a few bytes.
	jmp	short @insert_symbol

@expand_50:
	inc	macro_level		;Indicate expansion going on
;	Copy linebuf into cur_macro (to remember arguments)
	cmp	cmd_level,0
	jne	@expand_20		;Already been copied
	mov	di,offset DGROUP:cur_macro
	call	near ptr get_line_len
	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
@expand_20:
	call	near ptr get_macro_line ;Yes, copy it into the linebuf buffer
;	get_macro_line will set the appropriate value of the carry flag.

@expand_98:
; MUST NOT CHANGE CARRY FLAG AFTER THIS POINT.
	@restore
	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 is 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:
	@link	LINEBUF_SIZE
tempbuf	equ	<byte ptr [bp-LINEBUF_SIZE]>

; Copy the current string from the macro buffer into temp buffer.
	mov	bx,mac_ptr		;BX->address of buffer descriptor
	mov	cx,LINEBUF_SIZE 	;CX<-size of buffer
	lea	ax,tempbuf		;AX->destination address
	mov	si,ax			;SI->tempbuf (macro expansion)
	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 parameters.
; 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
;jmh, 4 December, 1997 - set last character to al (which should be zero because
;of the rep in strstk_copy) so testing the star in "%n*" will fail when "%n" is
;the last thing on the line and a '*' was leftover from a previous expansion.
	mov	bx,cx
	mov	byte ptr si[bx],al
	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:
	xor	ah,ah
	cmp	al,'*'
	jne	@get_macro_line_46
	inc	ah
	mov	al,ah
	jmp	short @get_macro_line_47

@get_macro_line_46:
	cmp	al,'9'
	ja	@get_macro_line_40	;Not a digit
	sub	al,'0'
	jb	@get_macro_line_40	;Not a digit
	cmp	byte ptr [si+1],'*'
	jne	@get_macro_line_47
	inc	si
	dec	cx
	inc	ah
@get_macro_line_47:
	dec	di			;Cancel stored PLACEHOLDER char
	mov	bx,di
	mov	dx,linelimit
	sub	dx,bx			;DX<-remaining space in buffer
	@save	si,cx
	mov	si,offset DGROUP:cur_macro ;SI->current macro being expanded
	mov	cx,cur_macro_len	;Length of original macro string
	call	near ptr getparms	;Get the argument into linebuf
					;AX<-num chars copied
					;CF indicates error condition
	jc	@get_macro_line_101	;Jump if getargs returned error
	@restore
@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 remaining characters

@get_macro_line_60:

	xchg	ax,di				;AX->Potential disp_end
	mov	lastchar,ax			;Update end of line
	mov	dot,ax				;Update cursor
; Set the lastchar and display end pointers
; (Reinstated by jmh 981106 since the first line of a macro can be expanded.)
	mov	dx,offset DGROUP:linebuf 	;Potential disp_begin
	call	near ptr set_disp_marks		;Set the marks

; Finally check to see if this line is the last in the macro expansion.
	mov	bx,mac_ptr		;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
	dec	macro_level		;Reset flag
@get_macro_line_90:
	@unlink
	clc				;Return macro expanded
	ret

@get_macro_line_101:
; truncation error
	jmp	near ptr line_too_long

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
	jmp	near ptr strstk_compare ;Is it the separator ?
;	strstk_compare sets ZF.
;	ret				;commented jmh 990517
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
	inc	del_one 			;Delete the symbol if it
	push	cx				;already exists
	call	execute_dels
	pop	cx
;	mov	bx,sym_ptr			;BX->stack descriptor
						;removed by jmh - set by dels
; Push macro name
	call	near ptr push_word		;Push first word onto
;						 the stack. Params :
;						 SI->string
;						 CX=num chars
;						 Returns:
;						 AL<-status code
;						 SI->char after word
;						 CX<-num remaining chars
	or	al,al				;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 separate		;Push separator onto stack
	jc	@execute_defs_109		;No room in stack
@execute_defs_99:
	ret

@execute_defs_109:
; Error. No room in symbol stack.
	mov	dx,offset noroom_sym
	jmp	near ptr cleanup		;Clear out partial definition
;	ret					;commented jmh 990517
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
;
; jmh 980507: partial rewrite
;-
execute_defm	proc near
	cmp	macro_level,0		;Expanding a macro ?
	je	@execute_defm_1		;No, jump
	mov	al,E_NESTED_MACRO
	jmp	near ptr abort_processing ;Yes, nested macro error

@execute_defm_1:
	@save	si,di
;	mov	bx,mac_ptr			;BX->stack descriptor
						;removed by jmh - set by delm
	inc	del_one 			;Delete the macro if it
	push	cx				;already exists
	call	execute_delm
	pop	cx
; Push macro name
	call	near ptr push_word		;Push first word onto
;						 the stack. Params :
;						 SI->string
;						 CX=num chars
;						 Returns:
;						 AL<-status code
;						 SI->char after word
;						 CX<-num remaining chars
	or	al,al				;Check status
	js	@execute_defm_90		;No room in stack
	jz	@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
	jc	@execute_defm_90		;Error

@execute_defm_40:
; Keep reading lines from the input and store in macro buffer.

; 	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
	call	near ptr get_line_len
	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
	je	@execute_defm_95

@execute_defm_50:
; Store line in macro buffer
	pop	cx				;CX<-length of line
	pop	si				;SI->linebuf
	mov	bx,mac_ptr			;BX->stack descriptor
	call	near ptr push_string
	jnc	@execute_defm_40		;No error

@execute_defm_90:
	mov	dx,offset noroom_mac
	call	near ptr cleanup		;Cleanup macro fragments
	jmp	short @execute_defm_99

@execute_defm_95:
; End of macro seen. Store macro separator
	pop	cx				;Restore the stack
	pop	si
	call	near ptr cmdmacro_separate
	jc	@execute_defm_90

@execute_defm_99:
	@restore
	ret
execute_defm endp



;+
; FUNCTION : cmdmacro_separate,separate
;
;	Pushes a separator string onto the stack.
;
; Parameters:
;	None for cmdmacro_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,mac_ptr			;BX->stack descriptor
separate	LABEL near
	mov	ax,sep_len			;AX<-length of separator string
	mov	dx,offset DGROUP:separator	;DX->separator string
	jmp	near ptr strstk_push		;Returns status in CF
;	ret					;commented jmh 990517
cmdmacro_separate endp



;+
; FUNCTION : 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.
;
; jmh 980507: display the error message here, removing disp_noroom proc.
;	  10: removed the individual calls since the descriptor was already set.
;
; Parameters:
;	BX -> buffer descriptor to clean
;	DX -> buffer name string
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
cleanup proc near
	push	dx				;Save message pointer
	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
	je	@cleanup_99			;Yes, then all done
	call	near ptr strstk_kill		;Kill string
	jmp	short @cleanup_10		;Keep going
@cleanup_99:
	pop	dx
	@DispStr
	mov	dx,offset noroom_msg
	int	21h
	ret
cleanup 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
	xchg	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:
	dec	cx
	jcxz	@search_variable_99		;If there was only one char
;						 after the marker, variable
;						 not possible
	lodsb					;AL<-next char
	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 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_symbol proc near
	@save	si
	mov	bx,sym_ptr			;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.
;	If DI is 0 returns ES:DI pointing to expansion (jmh).
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
get_dosenv proc near
	push	si
	push	di
	push	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
	push	ax				;jmh 980510: replaced the sub
	push	di				; and mov's with push's
	push	dx

	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
	call	near ptr strlen
	pop	di
	xchg	ax,cx				;AX<-length of expansion
	cmp	exp_buf,0			;these two lines added by jmh
	je	@get_dosenv_99a
	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
	pop	es
	pop	di
	pop	si
	ret
@get_dosenv_99a:
	mov	sp,bp
	pop	bp
	pop	si			;Don't restore ES:DI (jmh)
	pop	si
	pop	si
	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
	jcxz	@replace_var_markers_99
	mov	al,VAR_MARKER

@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
	loop	@replace_var_markers_10

@replace_var_markers_99:
	xchg	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
	@link	LINEBUF_SIZE+2
var_exp equ	<byte ptr [bp-LINEBUF_SIZE-2]>

@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
	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
	push	ax				;Remember expansion length
	mov	ax,var_len
	call	near ptr remove_chars		;SI,AX parameters
	pop	ax				;Restore length of expansion
	mov	di,si				;DI->destination
	lea	si,var_exp
	call	near ptr insert_chars		;SI,DI,AX parameters
	jnc	@expand_var_10			;No error
	mov	al,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)
	call	near ptr get_line_len
	mov	di,si
	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
	@unlink
	ret
expand_var endp



;+
; FUNCTION: expand_braces by jmh
;
;	Determine if the line has any braces ({}) and expand if necessary.
;
; Parameters:
;
; Returns:
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
expand_braces proc near
	@link	2+2+2+LINEBUF_SIZE
start_pre equ	<word ptr [bp-2]>
len_pre   equ	<word ptr [bp-4]>
len_post  equ	<word ptr [bp-6]>
post_buf  equ	<byte ptr [bp-6-LINEBUF_SIZE]>

@expand_brace_0:
	mov	si,offset DGROUP:linebuf
	mov	cx,lastchar		;End of line
	xor	ah,ah			;Use AH as a quote flag
@expand_brace_1:
	cmp	si,cx
	jne	@expand_brace_1a
@expand_brace_1b:
	jmp	@expand_brace_99	;The line has no braces
@expand_brace_1a:
	call	test_quote
	jnz	@expand_brace_1
	cmp	al,'{'
	jne	@expand_brace_1
	mov	di,si
	dec	di			;DI at the {
	cwd				;Brace count (sets DL = 0 since AH = 0)
@expand_brace_10:			;Search for the }
	cmp	si,cx
	je	@expand_brace_1b	;There isn't one
	call	test_quote
	jnz	@expand_brace_10
	call	@skip_braces
	jns	@expand_brace_10
	xchg	di,si			;SI at the {, DI at char after }
	mov	bx,si
	mov	dh,' '                  ;In case brace is at bol
@expand_brace_20:			;Find start of prepend
	cmp	si,offset DGROUP:linebuf
	je	@expand_brace_30
	std				;Searching characters backwards...
	call	test_quote
	cld				;...but forward required elsewhere
	jnz	@expand_brace_20
	call	@ispend
	jne	@expand_brace_20
	mov	dh,al			;Remember the terminator (assume that
					; DOS delimiters aren't used at the
					; beginning)
	inc	si
	inc	si			;SI == string to prepend
@expand_brace_30:
	mov	ax,bx
	sub	ax,si			;AX == length to prepend
	mov	start_pre,si
	mov	len_pre,ax
	mov	si,di			;Find the postpend
;	xor	ah,ah			;I'll assume len_pre is < 256
	mov	dl,ah			;Postpend brace count
@expand_brace_40:
	cmp	si,cx
	je	@expand_brace_50
	call	test_quote
	jnz	@expand_brace_40
	call	@skip_braces
	jnz	@expand_brace_40
	call	@ispend
	jne	@expand_brace_40
	call	isdelim 		;Delimiters shouldn't separate items
	jne	@expand_brace_45	;(corrected 980512)
	mov	al,' '
@expand_brace_45:
	dec	si
@expand_brace_50:
	or	ah,ah
	jnz	@expand_brace_99	;Postpend has a mismatched quote
	mov	cx,si
	sub	cx,di			;CX == length to postpend
	mov	len_post,cx
	mov	si,di			;Copy the postpend to the buffer
	lea	di,post_buf
	rep	movsb
	xchg	dh,al			;AL prepend terminator, DL post
	call	isspace 		;If the prepend terminator is a space
	jne	@expand_brace_51	;then use the postpend terminator to
	cmp	si,lastchar		;separate the items in the braces,
	je	@expand_brace_51	;unless the postpend was eol.
	mov	al,dh
@expand_brace_51:
	stosb
	inc	len_post

@expand_brace_60:
	mov	si,bx			;Delete the brace, which also provides
	mov	al,1			;the first prepend (AH is still 0)
	call	remove_chars
@expand_brace_61:
	xor	ah,ah
	cwd				;Brace count
@expand_brace_70:			;Find the end of the current item
	call	test_quote
	jnz	@expand_brace_70
	call	@skip_braces
	js	@expand_brace_75
	jnz	@expand_brace_70
	cmp	al,','
	jne	@expand_brace_70
	dec	si			;Remove the comma
	mov	al,1
	call	remove_chars
	mov	di,si			;Add the postpend
	lea	si,post_buf
	mov	ax,len_post
	call	@safe_insert
	mov	si,start_pre		;Add the prepend for the next item
	mov	ax,len_pre
	call	@safe_insert
	mov	si,di
	jmp	short @expand_brace_61

@expand_brace_75:
	dec	si			;Delete the brace, providing the last
	mov	al,1			;postpend and start again
	call	remove_chars
	jmp	@expand_brace_0

@expand_brace_99:
	@unlink
	ret

@skip_braces proc near
;Use DL to count nested braces. Will return ZF or PL if not inside a brace.
	cmp	al,'{'
	jne	@skip_braces_10
	inc	dx
@skip_braces_10:
	cmp	al,'}'
	jne	@skip_braces_99
	dec	dx
@skip_braces_99:
	or	dl,dl
	ret
@skip_braces endp

@ispend proc near
;Determine if the character will stop a pre- or postpend.
;Returns Z if so.
;980512: rewrote using the list.
	@save	di,cx
	mov	di,offset pend_list
	mov	cx,pend_len
	repne	scasb
	@restore
	ret
@ispend endp


@safe_insert proc near
;Perform an insert_chars, but exit on overflow, and update DI.
	push	ax			;Save the length
	call	insert_chars
	pop	ax
	jc	@safe_insert_1
	add	di,ax
	ret

@safe_insert_1:
	jmp	near ptr line_too_long
@safe_insert endp


expand_braces endp



;+
; FUNCTION: getparms
;
;	A getargs function for macros. I don't think quotes should be stripped
;	and I want some extra functionality, namely copying the command line
;	from a particular parameter.
;
; Paramaters:
;	SI -> the line
;	AL := parameter number (0 is the macro name)
;	AH := 0 for the parameter only
;	      1 for the line from the parameter
;	CX := length of line
;	BX -> buffer to store argument
;	DX := length of buffer
;
; Return:
;	Normal operation:
;		buffer at BX filled with argument(s); AX has length thereof.
;		CF = 0
;	Overflow:
;		CF = 1, buffer contents and AX undefined.
;
; Registers destroyed:
;	BX,CX,DX
;
; jmh 990522: with the advent of LFN, I wrote a separate function to get a
;	      string. Use that function here.
;-
getparms proc near
	@save	si,di
buf_len equ	<temp1>

	mov	buf_len,dx
	mov	dl,al			;DX has parameter wanted (assume DH==0)
	inc	dx			;Prime it
@getparms_10:
	push	dx
	call	near ptr get_string
	pop	dx
	jc	@getparms_99		;None left, return 0 length arg.
	dec	dx
	jnz	@getparms_10

;At this point: DI has the argument wanted, SI it's end,
;		CX has the length of the remainder of the line
	shr	ah,1
	jc	@getparms_20		;Want everything
	xor	cx,cx			;Nothing additional
@getparms_20:
	add	cx,si
	sub	cx,di			;CX = length of argument(s)
	cmp	cx,buf_len
	ja	@getparms_100		;Too big to fit
	mov	si,bx
	xchg	si,di
	push	cx
	rep	movsb
	pop	cx
@getparms_99:
	xchg	ax,cx
	stc
@getparms_100:
	cmc
	@restore
	ret
getparms endp


CSEG	ENDS

	END

