**************************************************************************
*
* The GNO Shell Project
*
* Developed by:
*   Jawaid Bazyar
*   Tim Meekins
*
* $Id: expand.asm,v 1.7 1998/12/31 18:29:13 tribby Exp $
*
**************************************************************************
*
* EXPAND.ASM
*   By Tim Meekins
*   Modified by Dave Tribby for GNO 2.0.6
*
* Command line expansion routines for wildcards and env vars
*
* Note: text set up for tabs at col 16, 22, 41, 49, 57, 65
*              |     |                  |       |       |       |
*	^	^	^	^	^	^	
**************************************************************************

	mcopy /obj/gno/bin/gsh/expand.mac

dummyexpand	start		; ends up in .root
	end

	setcom 60

**************************************************************************
*
* glob the command line and expand filename wildcard characters
*
**************************************************************************

glob	START
	
	using	vardata

count	equ	0
gname	equ	count+2
sepptr	equ	gname+4
eptr	equ	sepptr+4
exppath	equ	eptr+4
filesep	equ	exppath+4
shallweglob	equ	filesep+2
wordbuf	equ	shallweglob+2
ptr	equ	wordbuf+4
buf	equ	ptr+4
globflag	equ	buf+4
space	equ	globflag+2

	subroutine (4:cmd),space

; Allocate buffer to hold result
	jsl	alloc1024
	sta	buf
	stx	buf+2

; Check for noglob variable and exit if it's set to something.
	ldy	varnoglob
	beq	doglob

; Allocate a buffer, copy the command line into it, and return.
	pei	(cmd+2)
	pei	(cmd)
	pei	(buf+2)
	pei	(buf)
	jsr	copycstr
	jmp	bye

;
; noglob isn't set, so we can start globbing.
;
doglob	sta	ptr	ptr points to next
	stx	ptr+2	 position in buf.

	jsl	alloc1024	Create a word buffer.
	sta	wordbuf
	stx	wordbuf+2

	stz	globflag	globflag = no globbing done (yet).

;
; Find the beginning of the next word
;
findword	jsr	g_getbyte	Get character from command line.
	jeq	alldone
	if2	@a,eq,#' ',passthru
	if2	@a,eq,#009,passthru
	if2	@a,eq,#013,passthru
	if2	@a,eq,#010,passthru
	if2	@a,eq,#';',passthru
	if2	@a,eq,#'&',passthru
	if2	@a,eq,#'|',passthru
	if2	@a,eq,#'>',passthru
	if2	@a,eq,#'<',passthru

; It's not a simple pass-through character. See what needs to happen.

	stz	shallweglob
	ldy	#0
	bra	grabbingword

; For pass-through characters, just copy to output buffer.

passthru	jsr	g_putbyte
	bra	findword


;
; single out the next word [y is initialized above]
;
grabword	jsr	g_getbyte
grabbingword	if2	@a,eq,#"'",grabsingle
	if2	@a,eq,#'"',grabdouble
	if2	@a,eq,#'\',grabslash
	if2	@a,eq,#' ',procword
	if2	@a,eq,#009,procword
	if2	@a,eq,#013,procword
	if2	@a,eq,#010,procword
	if2	@a,eq,#000,procword
	if2	@a,eq,#';',procword
	if2	@a,eq,#'&',procword
	if2	@a,eq,#'|',procword
	if2	@a,eq,#'>',procword
	if2	@a,eq,#'<',procword
	if2	@a,eq,#'[',grabglob  
	if2	@a,eq,#']',grabglob
	if2	@a,eq,#'*',grabglob
	if2	@a,eq,#'?',grabglob

; Default action (also completion of some of the other special cases)
grabnext	sta	[wordbuf],y	Save character in word buffer
	iny		 and bump its index.
	bra	grabword	Get next character of word.

; "[", "]", "*", "?"
grabglob	ldx	#1	Set "shallweglob"
	stx	shallweglob	 flag.
	bra	grabnext	Store char in word buf & get next.

; "\"
grabslash	sta	[wordbuf],y	Save "\" in word buffer
	iny		 and bump its index.
	jsr	g_getbyte	Get next character in cmd line.
	beq	procword	If null byte, word is terminated.
	bra	grabnext	Store char in word buf & get next.

; '"'
grabsingle	sta	[wordbuf],y	Save char in word buffer
	iny		 and bump its index.
	jsr	g_getbyte	Get next character in cmd line.
	beq	procword	If null byte, word is terminated.
	if2	@a,eq,#"'",grabnext If "'", store and grab next char.
	bra	grabsingle	Save new char and stay in this loop.

; "'"
grabdouble	sta	[wordbuf],y	Save char in word buffer
	iny		 and bump its index.
	jsr	g_getbyte	Get next character in cmd line.
	beq	procword	If null byte, word is terminated.
	if2	@a,eq,#'"',grabnext If '"', store and grab next char.
	bra	grabdouble	Save new char and stay in this loop.


;
; The complete word is in the buffer. Time to process it.
;
procword	dec	cmd	Decrement cmd line pointer.
	lda	#0	Terminate word buffer with
	sta	[wordbuf],y	 a null byte.

;
; Shall we glob? Shall we scream? What happened, to our postwar dream?
;
	lda	[wordbuf]
	and	#$FF
	if2	@a,eq,#'-',skipdeglob	;This allows '-?' option.
	lda	shallweglob
	bne	globword
;
; we didn't glob this word, so copy the word buffer to the output buffer
;
skipdeglob	ldy	#0
flushloop	lda	[wordbuf],y
	and	#$FF
	beq	doneflush
	jsr	g_putbyte
	iny
	bra	flushloop
doneflush	jmp	findword

;
; Hello, boys and goils, velcome to Tim's Magik Shoppe
;
; Ok, here's the plan:
;  1. We give InitWildcardGS a PATHNAME.
;  2. NextWildcardGS returns a FILENAME.
;  3. We need to expand to the command-line the full pathname.
;  4. Therefore, we must put aside the prefix, and cat each file returned
;     from NextWildcardGS to the saved prefix, but, we must still pass
;     the entire path to InitWildcardGS.
;  5. This solves our problem with quoting. Expand the quotes before
;     passing along to InitWildcardGS, BUT the saved prefix we saved to cat
;     to will still have the quotes in it, so that the tokenizer can deal
;     with it. Whew!
;
; Well, here goes nuthin'....  [Ya know, and I'm reading Levy's book 
; 'Hackers' right now...]
;
;
;
; Expand out the quoted stuff, and keep an eye out for that ubiquitous last
; filename separator... then we can isolate him!
;
globword	stz	filesep	
	jsl	alloc1024	Alloc buffer for wildcard pattern.
	sta	exppath
	stx	exppath+2

	incad	@xa	Leave room for length word
	incad	@xa
	sta	eptr
	stx	eptr+2
	sta	sepptr
	stx	sepptr+2
		
	ldy	#0
exploop	lda	[wordbuf],y
	and	#$FF
	beq	endexp
	iny
	if2	@a,eq,#'\',expslash
	if2	@a,eq,#"'",expsingle
	if2	@a,eq,#'"',expdouble
	if2	@a,eq,#'/',expsep
	if2	@a,eq,#':',expsep
expput	sta	[eptr]
	incad	eptr
	bra	exploop
expsep	sty	filesep
	sta	[eptr]
	incad	eptr
	mv4	eptr,sepptr
	bra	exploop
expslash	lda	[wordbuf],y
	iny
	and	#$FF
	beq	endexp
	bra	expput
expsingle	lda	[wordbuf],y
	iny
	and	#$FF
	beq	endexp
	if2	@a,eq,#"'",exploop
	sta	[eptr]
	incad	eptr
	bra	expsingle
expdouble	lda	[wordbuf],y
	iny
	and	#$FF
	beq	endexp
	if2	@a,eq,#'"',exploop
	sta	[eptr]
	incad	eptr
	bra	expdouble
;
; We really didn't mean to expand the filename, so, copy it back again..
;
endexp	ldy	filesep
copyback	lda	[wordbuf],y
	iny
	and	#$FF
	sta	[sepptr]
	incad	sepptr
	cmp	#0
	bne	copyback
;
; Calculate length by subtracting sepptr from starting address
;
	sub2	sepptr,exppath,@a
	dec2	a	Don't count length word or \0
	dec	a
	sta	[exppath]
;
; We now have enough to call InitWildCardGS!!!
; [ let's mutex the rest so we don't have to fix InitWC and NextWC ;-) ]
;
	lock	glob_mutex
;
; Call shell routine InitWildcardGS to initialize the filename pattern
;
	stz	count
	mv4	exppath,initWCpath
	InitWildcardGS initWCparm

	ph4	#65	Allocate memory for
	~NEW		expanded name.
	sta	gname	Store in direct
	stx	gname+2	 page pointer
	sta	nWCname	  and in NextWildcardGS
	stx	nWCname+2	   parameter block.
	lda	#65	Store maximum length at
	sta	[gname]	 beginning of result buf.
;
; Call shell routine NextWildcardGS to get the next name that matches.
;
WCloop	NextWildcardGS nWCparm
	ldy	#2
	lda	[gname],y
	beq	nomore
;
; Keep count of how many paths are expanded
;
	inc	count

;
; Copy the original path (up to file separator) to output buffer
;
	ldy	#0
outtahere	if2	@y,eq,filesep,globout
	lda	[wordbuf],y
	jsr	g_putspecial
	iny
	bra	outtahere
;
; Copy the expanded filename to output buffer
;
globout	ldy	#2	Get returned length
	lda	[gname],y	 from GS/OS buffer.
	tax
	ldy	#4
globoutta	lda	[gname],y	Copy next character
	jsr	g_putspecial	 into buffer.
	iny
	dex
	bne	globoutta
;
; Place blank as separator after name and see if more are expanded.
;
	lda	#' '
	jsr	g_putbyte
	bra	WCloop

;
; All of the names (if any) from this pattern have been expanded.
;
nomore	anop

	unlock glob_mutex
;
; Deallocate path buffers (we should probably alloc once, not each word... )
;
	pei	(gname+2)
	pei	(gname)
	jsl	nullfree
	ldx	exppath+2
	lda	exppath
	jsl	free1024

	lda	count	If something was expanded,
	beq	nothingfound

	lda	globflag	Set "globbed, something found"
	ora	#$8000	 bit in globflag.
	sta	globflag
	jmp	findword	  Go find the next word.

; Nothing was expanded from the wildcard.  If we wanted to act like
; ksh, we could pass the original text by doing a "jmp skipdeglob".

nothingfound	anop
	lda	globflag	Set "globbed, nothing found"
	ora	#$4000	 bit in globflag.
	sta	globflag

	jmp	findword	Go find the next word.

;
; Goodbye, cruel world, I'm leaving you today, Goodbye, goodbye.
;
alldone	jsr	g_putbyte	Store null byte at end of string.

;
; Check globflag for no valid matches found in any pattern
;
	lda	globflag
	cmp	#$4000
	bne	alldone2

	ldx	#^nomatch
	lda	#nomatch
	jsr	errputs

	ldx	buf+2
	lda	buf
	jsl	free1024

	stz	buf+2
	stz	buf

alldone2	ldx	wordbuf+2
	lda	wordbuf
	jsl	free1024

bye	return 4:buf


;
; Subroutine of glob: get a byte from the original command-line
;
g_getbyte	lda	[cmd]
	incad	cmd
	and	#$FF
	rts

;
; Subroutine of glob: put special characters. Same as g_putbyte, but
; if it is a special shell character then quote it.
;
g_putspecial	and	#$7F
	if2	@a,eq,#' ',special
	if2	@a,eq,#'.',special
	if2	@a,eq,#013,special
	if2	@a,eq,#009,special
	if2	@a,eq,#';',special
	if2	@a,eq,#'&',special
	if2	@a,eq,#'<',special
	if2	@a,eq,#'>',special
	if2	@a,eq,#'|',special
	bra	g_putbyte
special	pha
	lda	#'\'
	jsr	g_putbyte
	pla		; fall through to g_putbyte...
;
; Subroutine of glob: store a byte into the new command-line
;
g_putbyte	short	a
	sta	[ptr]
	long	a
	incad	ptr
	rts


glob_mutex	key

;
; Parameter block for shell InitWildcardGS call (p 414 in ORCA/M manual)
;
InitWCParm	dc	i2'2'	pCount
InitWCPath	ds	4	Path name, with wildcard
	dc	i2'%00000001'	Flags (this bit not documented!!!)

;
; Parameter block for shell NextWildcardGS call (p 414 in ORCA/M manual)
;
nWCparm	dc	i2'1'	pCount
nWCname	ds	4	Pointer to returned path name

nomatch	dc	c'No match',h'0d00'

	END

**************************************************************************
*
* Expand $variables and tildes not in single quotes
*
**************************************************************************

expandvars	START

; Maximum number of characters in an environment variable or $<
MAXVAL	equ	512


ptr	equ	1
buf	equ	ptr+4
dflag	equ	buf+4
space	equ	dflag+2
cmd	equ	space+3
end	equ	cmd+4

;	 subroutine (4:cmd),space

	tsc
	sec
	sbc	#space-1
	tcs
	phd
	tcd

	stz	dflag	Delimiter flag = FALSE.

	jsl	alloc1024
	sta	buf
	sta	ptr
	stx	buf+2
	stx	ptr+2

loop	jsr	e_getbyte
	jeq	done
	if2	@a,eq,#"'",quote
	if2	@a,eq,#'$',expand
	if2	@a,eq,#'~',tilde
	if2	@a,eq,#'\',slasher
	jsr	e_putbyte
	bra	loop

slasher	jsr	e_putbyte
	jsr	e_getbyte
	jsr	e_putbyte
	bra	loop

quote	jsr	e_putbyte
	jsr	e_getbyte
	jeq	done
	if2	@a,ne,#"'",quote
	jsr	e_putbyte
	bra	loop

;
; Tilde expansion: use the contents of $home, but make sure the
; path delimiter(s) match what the user wants (either "/" or ":")
;
tilde	anop
	lock	exp_mutex

	lda	#"oh"	Strangely enough,
	sta	name	 this spells "home"!
	lda	#"em"
	sta	name+2
	sta	dflag	Delimiter flag = TRUE.
	ldx	#4
	jmp	getval

;
; Expand an environment variable since a '$' was encountered.
;
expand	anop
	lock	exp_mutex

	lda	#0
	sta	name
	lda	[cmd]
	and	#$FF
	if2	@a,eq,#'{',braceexpand
	if2	@a,eq,#'<',stdinexpand
	ldx	#0
nameloop	lda	[cmd]
	and	#$FF
	beq	getval
	if2	@a,cc,#'0',getval
	if2	@a,cc,#'9'+1,inname
	if2	@a,cc,#'A',getval
	if2	@a,cc,#'Z'+1,inname
	if2	@a,eq,#'_',inname
	if2	@a,cc,#'a',getval
	if2	@a,cc,#'z'+1,inname
	bra	getval
inname	jsr	e_getbyte
	cpx	#255	Only the first 255 characters
	beq	nameloop	 are significant.
	sta	name,x
	inx
	bra	nameloop

;
; expand in braces {}
;
braceexpand	jsr	e_getbyte
	ldx	#0
braceloop	lda	[cmd]
	and	#$FF
	beq	getval
	jsr	e_getbyte
	if2	@a,eq,#'}',getval
	cpx	#255	Only the first 255 characters
	beq	braceloop	 are significant.
	sta	name,x
	inx
	bra	braceloop

;
; get text from standard input
;
stdinexpand	jsr	e_getbyte
	ReadLine (#value,#MAXVAL,#13,#0),@a
	sta	valueln
	bra	chklen

;
; Get a value for this variable
;
getval	stx	nameln	Save length of name.
	ReadVariableGS ReadVarPB	Read its value.

	lda	valueln	Get value length.
	cmp	#MAXVAL+1	If > maximum allowed length,
	bcs	expanded	 we didn't get anything.
	cmp	#0
chklen	anop
	beq	expanded	If 0, nothing to do
	tax		Save length in X-reg.
	lda	dflag	If delimiter flag isn't set,
	beq	storeval	 go store the variable value

; Check to see if delimiters in the variable need to be switched
	lda	valueln	Set up length in
	tax		  X-reg and get
	lda	[cmd]	    next command line
	and	#$FF	      character.
	cmp	#"/"	If it's a slash, see if
	beq	chkvarslash	 variable needs to convert to slash.
	cmp	#":"	If it's not a colon,
	bne	storeval	 no need to convert.
	lda	value	Get first character of value.
	and	#$FF
	cmp	#"/"	If it's not a slash,
	bne	storeval	 no need to convert.

; Convert variable from "/" to ":" delimiter
	short m
chk_s	lda	value-1,x
	cmp	#"/"
	bne	bump_s
	lda	#":"
	sta	value-1,x
bump_s	dex
	bpl	chk_s
	long	m
	bra	storeval

chkvarslash	anop
	lda	value	Get first character of value.
	and	#$FF
	cmp	#":"	If it's not a colon,
	bne	storeval	 no need to convert.

; Convert variable from ":" to "/" delimiter
	short m
chk_c	lda	value-1,x
	cmp	#":"
	bne	bump_c
	lda	#"/"
	sta	value-1,x
bump_c	dex
	bpl	chk_c
	long	m

;
; Store the variable's value in the out buffer
;
storeval	anop
	lda	valueln	Get length.
	tay		Save length in Y-reg.
	ldx	#0	Use X-reg in index value.
putval	lda	value,x
	jsr	e_putbyte
	inx
	dey	
	bne	putval

expanded	unlock exp_mutex
	stz	dflag	Delimiter flag = FALSE.
	jmp	loop

done	jsr	e_putbyte

	ldx	buf+2
	ldy	buf

	lda	space
	sta	end-3
	lda	space+1
	sta	end-2
	pld
	tsc
	clc
	adc	#end-4
	tcs

	tya
	rtl

e_getbyte	lda	[cmd]
	incad	cmd
	and	#$FF
	rts

e_putbyte	short	a
	sta	[ptr]
	long	a
	incad	ptr
	rts

exp_mutex	key


; GS/OS string to hold variable name
namestr	anop
nameln	ds	2	Length of name
name	ds	256	Room for 255 chars + 0.


; GS/OS result buffer to hold up to MAXVAL bytes of value
valueresult	anop
	dc	i2'MAXVAL+4'	Length of result buffer
valueln	ds	2	Length of value
value	ds	MAXVAL	Room for MAXVAL chars


; Parameter block for shell ReadVariableGS calls
ReadVarPB	anop
	dc	i2'3'	pCount
RVname	dc	a4'namestr'	Name (pointer to GS/OS string)
RVvalue	dc	a4'valueresult'	Value (ptr to result buf or string)
RVexport	ds	2	Export flag

	END
