; UTL.ASM
; (c) 1989, 1990 Ashok P. Nadkarni
;
; General utility functions for CMDEDIT. SMALL model only. Also assume 
; ES == DS. 
;

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

	PUBLIC	stre_cmp
	PUBLIC	strlen			;added by jmh 980623
	PUBLIC	strsilen		;added by jmh 990516
	PUBLIC	tolower
	PUBLIC	xlate_lower
	PUBLIC	isalphnum
	PUBLIC	isspace
	PUBLIC	isdelim
	PUBLIC	bell
	PUBLIC	push_string
	PUBLIC	push_word
	PUBLIC	skip_nonwhite
	PUBLIC	skip_whitespace
	PUBLIC	skip_nondelim
	PUBLIC	output_newline
	PUBLIC	dosify_line		; added by wd
	PUBLIC	match_ext		;added by jmh 980513
	PUBLIC	get_string		;added by jmh 990522
	PUBLIC	test_quote		;moved from cmdmacro.asm, jmh 990522
	
	EXTRN	silent:BYTE
	EXTRN	lastchar:WORD
	EXTRN	linebuf:BYTE
	EXTRN	delim_list:BYTE 	;these two by jmh 980512
	EXTRN	delim_len:ABS
	EXTRN	crlf$:BYTE		;added by jmh 011219


CSEG	SEGMENT	BYTE PUBLIC 'CODE'

DGROUP	GROUP	CSEG

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

;+
; FUNCTION : stre_cmp
;
;	Does a case-insensitive comparison of two strings of equal length.
;
; Parameters:
;	DS:SI :=	Address of string 1.
;	ES:DI :=	Address of string 2.
;	CX    :=	Length.
;
; Returns:
;	If string 1 = string 2, ZF = 1, CF = 0.
;	If string 1 < string 2, ZF = 0, CF = 1.
;	If string 1 > string 2, ZF = 0, CF = 0.
;
; Registers AX,CX destroyed.
;-
stre_cmp proc near
	@save	si,di
	xor	ax,ax			;Clear flags
	jcxz	@stre_cmp_99
@stre_cmp_10:
	lodsb				;String 1 byte
	call	near ptr tolower	;al := Lowercase version
	mov	ah,al			;Save it.
	mov	al,ES:[di]		;Ditto for string 2
	inc	di
	call	near ptr tolower	;al := Lowercase version
	cmp	ah,al			;Compare string 1 with string 2
	loope	@stre_cmp_10		;Keep looping as long as equal
@stre_cmp_99:
	@restore
	ret
stre_cmp endp



;+
; FUNCTION : strlen by jmh 980623
;
;	Determine the length of a null-terminated (ASCIIZ) string.
;
; Parameters:
;	DI :=	address of string
;
; Returns:
;	CX :=	length of string
;	DI :=	address of character after NUL
;
; Registers destroyed:
;	AL (0)
;-
strsilen label near
	mov	di,si
strlen proc near
	xor	al,al			;Search for NUL
	mov	cx,0ffffh
	repne	scasb			;DI->byte after NUL
	not	cx
	dec	cx			;CX<-length of string
	ret
strlen endp



;+
; FUNCTION : tolower
;
;	Converts the character in AL to lower case if it is a upper case
;	character, else leaves it unchanged.
;
; Parameters:
;	Al :=	character
;
; Returns:
;	AL :=	lowercase version or unchanged
;-
tolower	proc near
	cmp	al,'Z'
	ja	@tolower_99
	cmp	al,'A'
	jb	@tolower_99
	add	al,20h
@tolower_99:
	ret
tolower	endp



;+
; FUNCTION : xlate_lower
;
;	Converts the passed string to lower case.
;
; Parameters:
;	CX :=	length of string	(jmh 980512: changed from AX)
;	SI :=	address of string
;
; Returns:
;	Nothing.
;
; Registers destroyed:
;	AL
;-
xlate_lower proc near
	@save	si,cx
	jcxz	@xlate_lower_99

@xlate_lower_10:
	mov	al,[si]
	call	near ptr tolower
	mov	[si],al
	inc	si
	loop	@xlate_lower_10
	
@xlate_lower_99:
	@restore
	ret
xlate_lower endp



;+
; FUNCTION : isalphnum
;
;	Test if the character is alphanumeric.
;
; Parameters:
;	AL	= character
;
; Returns:
;	ZF	= 0 if alphanumeric (changed from CF by jmh)
;		  1 if not
;
; Registers destroyed:
;	AL
;-
isalphnum proc near
	call	near ptr tolower	;Save bytes by only testing lowercase
	sub	al,'a'
	cmp	al,26
	jb	@isalphnum_99		;A letter
	add	al,'a'-'0'
	cmp	al,10
	jb	@isalphnum_99		;A digit
stz:					;Used by isnotspace
	cmp	al,al			;Set ZF
@isalphnum_99:				;NZ is already set
	ret
isalphnum endp



;+
; FUNCTION : isspace, isnotspace
;
;	Check if a character is (not) a SPACE or a TAB
;
; Parameters:
;	AL	= character to check
;
; Returns:
;	ZF	= 1 if AL is (not) a space or a tab
;		  0 otherwise
;
; Register(s) destroyed:
;	AL (isnotspace only)
;-
isspace	proc	near
	cmp	al,TAB
	je	@isspace_99
	cmp	al,SPACE
@isspace_99:
	ret
isspace	endp

isnotspace proc near
	call	isspace
	jne	stz
	inc	ax
	ret
isnotspace endp



;+
; FUNCTION : isdelim
;
;	Check if a character is an MSDOS delimiter.
;
; Parameters:
;	AL	= character to check
;
; Returns:
;	ZF	= 1 if AL is a delimiter
;		  0 otherwise
;
; Register(s) destroyed:
;
; jmh 980512: rewrote using the list
;-
isdelim proc	near
	@save	di,cx
	mov	di,offset delim_list
	mov	cx,delim_len
	repne	scasb
	@restore
	ret
isdelim	endp



;+ FUNCTION : skip_whitespace, skip_nonwhite, skip_nondelim
;
;	Searches for the next non-whitespace, whitespace or delimiter in a
;	given string.
;
; Parameters:
;	SI	-> pointer to string
;	CX	== num chars in the string
;
; Returns:
;	CF	= 1 if end-of string reached else 0
;	SI	->next whitespace character or end-of-string
;	CX	<-num remaining characters including one pointed to by SI
;
; Register(s) destroyed:
;	AX,DX
;-
skip_non proc near

skip_nonwhite LABEL near
	mov	dx,offset DGROUP:isspace
	jmp	short @skip_non	

skip_nondelim LABEL near
	mov	dx,offset DGROUP:isdelim
	jmp	short @skip_non

skip_whitespace LABEL near
	mov	dx,offset DGROUP:isnotspace

@skip_non:
	jcxz	@skip_non_98			;Empty string
@skip_non_10:
	lodsb					;AL<-next char
	call	dx				;Test it
	loopne	@skip_non_10			;Repeat until the test succeeds
	jne	@skip_non_98			;End-of-string
; non-whitespace, whitespace or delimiter found
	dec	si				;SI->points to it
	inc	cx				;CX<-remaining number of bytes
	clc					;CF<-0 (char found)
	ret

@skip_non_98:					; End of string reached.
	stc
	ret
skip_non endp



;+
; FUNCTION : push_word
;
;	Looks for the next word (delimited by whitespace) and pushes it
;	onto the specified string stack.
;
; Parameters:
;	BX	-> strstack descriptor
;	SI	-> string
;	CX	== length of string (< 256)
;
; Returns:
;	AL	<- 0 if no errors
;		  -1 if no room in stack
;		  +1 if no word in string
;	SI	-> char after first word (or end-of-string)
;	CX	<- num remaining characters
;
; Register(s) destroyed:
;	DX
;-
push_word proc	near
; Skip forward to first word
	call	near ptr skip_whitespace	;Returns
;						 SI->start of word
;						 CX<-remaining chars
	mov	al,1				;Code for blank line
	jcxz	@push_word_99			;No words in line
	push	si				;Save start of word
	push	cx				;Save count
	call	near ptr skip_nonwhite		;Find end of word
;						 SI->beyond word
;						 CX<-remaining chars
	pop	ax
	sub	ax,cx				;AX<-length of word
	pop	dx				;DX->start of word
	push	cx				;Save remaining char count
	call	near ptr strstk_push		;Store macro name into
;						 macro stack. Params
;						 AX,BX,DX
;						 Returns Cf = 0 or 1
	pop	cx				;CX<-remaining character
	sbb	al,al				;AL = 0 if NC, -1 if CF

@push_word_99:
	ret
push_word	endp



;+
; FUNCTION : push_string
;
;	Pushed the specified string onto the specified stack.
;
; Parameters:
;	BX	-> strstack descriptor
;	SI	-> string
;	CX	== length of string must be < 256
;
; Returns:
;	CF	<- 0 if no errors
;		   1 if no room in stack
;
; Register(s) destroyed:
;	AX,CX,DX
;-
push_string proc near
	mov	dx,si				;DX->start of string
	xchg	ax,cx				;AX<-length of string
	jmp	near ptr strstk_push		;Store macro name into
;						 macro stack. Params
;						 AX,BX,DX
;						 Returns Cf = 0 or 1
;	ret
push_string	endp



;+
; FUNCTION : bell
;
;	Called to ring the bell.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
;
; Registers destroyed:
;	AX
;-
bell	proc	near
	rol	silent,1
	jc	@bell_99
	mov	ax,0e07h		;jmh 980518: use teletype
	int	10h			;Ignore page and attribute
@bell_99:
	ret
bell	endp


;+
; FUNCTION: output_newline
;
; Registers destroyed:
;	AX,DX
;-
output_newline proc near
	@DispStr crlf$			;changed by jmh 011219
	ret
output_newline endp


;+
; FUNCTION: dosify_line by wd
;
; Parameters:
;	SI -> string to massage
;	CX =  line length
;
; Registers destroyed:
;	AL
;-
dosify_line proc near
	jcxz	@dosify_line_99
	push	cx
	push	si
@dosify_line_10:
	lodsb				;al = currentchar
	cmp	al,'/'			;is this a slash?
	je	@dosify_line_20		;yes, change it
	cmp	al,'\'			;is this a backslash?
	je	@dosify_line_25		;yes, check it
	cmp	al,'-'			;is this a dash
	jne	@dosify_line_40		;no, leave it alone

	cmp	byte ptr [si-2],' '	;is lastchar a space?
	jne	@dosify_line_40		;no, leave it alone
	mov	al,'/'			;yes, change " -" to " /"
	jmp	short @dosify_line_30
@dosify_line_20:
	mov	al,'\'			;change '/' to '\'
@dosify_line_25:			;see if this is a trailing slash
	cmp	cl,1			;is it at the end of the line?
	jne	@dosify_line_26 	;jmh 980514: added following 7 lines
	cmp	si,offset linebuf+1	;is it the only character?
	je	@dosify_line_30 	;yes, leave it alone
	cmp	byte ptr [si-2],':'     ;does it follow a colon
	je	@dosify_line_30
	cmp	byte ptr [si-2],' '     ; or a space?
	je	@dosify_line_30 	;yes, leave it alone
	jmp	short @dosify_line_27	;chop it off
@dosify_line_26:
	cmp	byte ptr [si],' '	;is it before a space?
	jne	@dosify_line_30		;no, leave it alone
@dosify_line_27:
	mov	al,' '			;change trailing slash to a space
@dosify_line_30:
	mov	[si-1],al		;store the revised character
@dosify_line_40:
	loop	@dosify_line_10
	pop	si
	pop	cx
@dosify_line_99:
	ret
dosify_line endp



;+
; FUNCTION : match_ext
;
;	Determine if an extension is in a list of extensions.
;
; Parameters:
;	DS:SI	= Extension
;	CX	= length of extension
;	ES:DI	= Dot ('.') separated, null-terminated list of extensions
;
; Returns:
;	ZF	= 1 for a match
;		  0 otherwise
;
; Registers destroyed:
;	AX
;-
match_ext proc near
	@save	di,bx
@match_ext_0:
	xor	bx,bx			;Length of matching extension
@match_ext_1:
	mov	al,es:[di+bx]		;Find the length of the extension to
	cmp	al,'.'                  ;be matched
	je	@match_ext_2
	or	al,al
	je	@match_ext_2
	inc	bx
	jmp	short @match_ext_1
@match_ext_2:
	cmp	bx,cx			;Lengths the same?
	jne	@match_ext_98		;Nope
	call	stre_cmp
	mov	cx,bx
	je	@match_ext_99
@match_ext_98:
	lea	di,[di+bx+1]		;Point to the next extension
	cmp	es:[di-1],bh		;Zero for end of list
	jnz	@match_ext_0		;Not yet
	inc	bx			;Clear the Z flag
@match_ext_99:
	@restore
	ret
match_ext endp



;+
; FUNCTION: get_string
;
;	Return pointers to the beginning and end of a string of characters.
;	The string is delimited by whitespace or enclosed in quotes. Opening
;	and closing quotes are stripped; quotes inside strings are not.
;	eg:
;	    `"abc def"' --> `abc def'
;	    `abc" "def' --> `abc" "def'
;	    `" "abc'    --> ` "abc'
;	    `abc" "'    --> `abc" '
;	Complain to make me change this behaviour.
;
; Parameters:
;	SI -> line containing string
;	CX := length of line
;	AH := bit 1 = 0 to keep quotes
;		      1 to strip quotes
;	      bit 2 = 0
;
; Returns:
;	CF = 0
;	  DI -> beginning of string
;	  SI -> end of string, plus one
;	  CX := remaining line length (only valid if keeping quotes)
;	CF = 1
;	  SI -> end of line, plus one
;	  DI unchanged
;	  CX := zero
;
; Registers destroyed:
;	AX,DX
;-
get_string proc near
	call	skip_whitespace
	stc
	jcxz	@find_word_90		;Blank line, return with carry
	cmp	byte ptr [si],QUOTE	;Opening quote?
	jne	@find_word_10		;No
	test	ah,2			;Stripping quotes?
	jz	@find_word_10
	inc	si			;Yep, point past it,
	dec	cx			; one character less
	or	ah,4			; and set the flag
@find_word_10:
	push	si			;Remember the start of the string
@find_word_20:
	jcxz	@find_word_30		;SI -> at lastchar, if CX == 0
	call	test_quote
	loopnz	@find_word_20
	call	isspace
	jne	@find_word_20
	dec	si
	inc	cx
@find_word_30:
	cmp	byte ptr [si-1],QUOTE	;Closing quote?
	jne	@find_word_40		;No
	test	ah,2			;Stripping quotes?
	jz	@find_word_40
	dec	si			;Yep, don't include it
@find_word_40:
	pop	di
	clc
@find_word_90:
	ret
get_string endp



;+
; FUNCTION: test_quote
;
;	Determine if a character is inside a quote.
;
; Parameters:
;	SI -> pointer to character
;	AH := bit 2: current quote state
;
; Returns:
;	SI -> points to next character
;	AL := character
;	AH := bit 2: new quote state
;	ZF if not in quote
;	NZ if in quote
;
; Registers destroyed:
;	None.
;-
test_quote proc near
	lodsb
	cmp	al,QUOTE
	jne	@test_quote_1
	xor	ah,4			;Toggle the quote flag
@test_quote_1:
	test	ah,4			;If we're in a quote
	ret				;then return NZ
test_quote endp



CSEG	ENDS

	END

